diff --git a/.gitattributes b/.gitattributes index 6313b56c578..506da7cc196 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,11 @@ * text=auto eol=lf +*.gen.ts linguist-generated +*_gen.ts linguist-generated +*_gen.go linguist-generated +*_gen.csv linguist-generated +*_gen.json linguist-generated +**/openapi_snapshots/*.json linguist-generated +apps/**/pkg/apis/*_manifest.go linguist-generated +public/openapi3.json linguist-generated +public/api-merged.json linguist-generated +public/api-enterprise-spec.json linguist-generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9cf3235c27f..0ccf1ab5333 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -98,6 +98,7 @@ /apps/correlations @grafana/datapro /apps/example/ @grafana/grafana-app-platform-squad /apps/logsdrilldown/ @grafana/observability-logs +/apps/annotation/ @grafana/grafana-backend-services-squad /pkg/api/ @grafana/grafana-backend-group /pkg/apis/ @grafana/grafana-app-platform-squad /pkg/apis/query @grafana/grafana-datasources-core-services @@ -151,7 +152,7 @@ /pkg/promlib @grafana/oss-big-tent /pkg/storage/ @grafana/grafana-search-and-storage /pkg/storage/secret/ @grafana/grafana-operator-experience-squad -/pkg/services/annotations/ @grafana/grafana-search-and-storage +/pkg/services/annotations/ @grafana/grafana-backend-services-squad /pkg/services/apikey/ @grafana/identity-squad /pkg/services/cleanup/ @grafana/grafana-backend-group /pkg/services/contexthandler/ @grafana/grafana-backend-group @grafana/grafana-app-platform-squad @@ -226,6 +227,7 @@ /devenv/datasources.yaml @grafana/grafana-backend-group /devenv/datasources_docker.yaml @grafana/grafana-backend-group /devenv/dev-dashboards-without-uid/ @grafana/dashboards-squad +/devenv/scopes/ @grafana/grafana-operator-experience-squad /devenv/dev-dashboards/annotations @grafana/dataviz-squad /devenv/dev-dashboards/migrations @grafana/dataviz-squad @@ -242,6 +244,7 @@ /devenv/dev-dashboards/panel-library @grafana/dataviz-squad /devenv/dev-dashboards/panel-piechart @grafana/dataviz-squad /devenv/dev-dashboards/panel-stat @grafana/dataviz-squad +/devenv/dev-dashboards/panel-status-history @grafana/dataviz-squad /devenv/dev-dashboards/panel-table @grafana/dataviz-squad /devenv/dev-dashboards/panel-timeline @grafana/dataviz-squad /devenv/dev-dashboards/panel-timeseries @grafana/dataviz-squad @@ -251,7 +254,6 @@ /devenv/dev-dashboards/all-panels.json @grafana/dataviz-squad /devenv/dev-dashboards/dashboards.go @grafana/dataviz-squad /devenv/dev-dashboards/home.json @grafana/dataviz-squad - /devenv/dev-dashboards/datasource-elasticsearch/ @grafana/partner-datasources /devenv/dev-dashboards/datasource-opentsdb/ @grafana/partner-datasources /devenv/dev-dashboards/datasource-influxdb/ @grafana/partner-datasources @@ -473,24 +475,12 @@ i18next.config.ts @grafana/grafana-frontend-platform /e2e-playwright/fixtures/long-trace-response.json @grafana/observability-traces-and-profiling /e2e-playwright/fixtures/tempo-response.json @grafana/oss-big-tent /e2e-playwright/fixtures/prometheus-response.json @grafana/datapro -/e2e-playwright/panels-suite/canvas-scene.spec.ts @grafana/dataviz-squad +/e2e-playwright/panels-suite/ @grafana/dataviz-squad /e2e-playwright/panels-suite/dashlist.spec.ts @grafana/grafana-search-navigate-organise -/e2e-playwright/panels-suite/datagrid-data-change.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/datagrid-editing-features.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/frontend-sandbox-panel.spec.ts @grafana/plugins-platform-frontend -/e2e-playwright/panels-suite/geomap-layer-types.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/geomap-map-controls.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/heatmap.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/panelEdit_base.spec.ts @grafana/dashboards-squad /e2e-playwright/panels-suite/panelEdit_queries.spec.ts @grafana/dashboards-squad /e2e-playwright/panels-suite/panelEdit_transforms.spec.ts @grafana/datapro -/e2e-playwright/panels-suite/state-timeline.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/table-footer.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/table-kitchenSink.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/table-markdown.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/table-sparkline.spec.ts @grafana/dataviz-squad -/e2e-playwright/panels-suite/table-utils.ts @grafana/dataviz-squad /e2e-playwright/plugin-e2e/ @grafana/oss-big-tent @grafana/partner-datasources /e2e-playwright/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend /e2e-playwright/smoke-tests-suite/ @grafana/grafana-frontend-platform @@ -559,6 +549,7 @@ i18next.config.ts @grafana/grafana-frontend-platform /packages/grafana-data/src/geo/ @grafana/dataviz-squad /packages/grafana-data/src/monaco/ @grafana/partner-datasources /packages/grafana-data/src/panel/ @grafana/dashboards-squad +/packages/grafana-data/src/panel/suggestions/ @grafana/dataviz-squad /packages/grafana-data/src/query/ @grafana/grafana-datasources-core-services /packages/grafana-data/src/rbac/ @grafana/access-squad /packages/grafana-data/src/table/ @grafana/dataviz-squad @@ -567,6 +558,7 @@ i18next.config.ts @grafana/grafana-frontend-platform /packages/grafana-data/src/transformations/ @grafana/datapro /packages/grafana-data/src/types/ @grafana/grafana-frontend-platform /packages/grafana-data/src/types/scopes.ts @grafana/grafana-operator-experience-squad +/packages/grafana-data/src/types/suggestions.ts @grafana/dataviz-squad /packages/grafana-data/src/utils/__snapshots__/ @grafanabot /packages/grafana-data/src/utils/anyToNumber.ts @grafana/grafana-frontend-platform /packages/grafana-data/src/utils/arrayUtils* @grafana/grafana-frontend-platform @@ -951,6 +943,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform /public/app/features/notifications/ @grafana/grafana-search-navigate-organise /public/app/features/org/ @grafana/grafana-search-navigate-organise /public/app/features/panel/ @grafana/dashboards-squad +/public/app/features/panel/suggestions/ @grafana/dataviz-squad /public/app/features/playlist/ @grafana/dashboards-squad /public/app/features/plugins/ @grafana/plugins-platform-frontend /public/app/features/profile/ @grafana/grafana-frontend-platform diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml index 49c1f44477b..2c6b46606f4 100644 --- a/.github/actions/change-detection/action.yml +++ b/.github/actions/change-detection/action.yml @@ -31,6 +31,9 @@ outputs: dockerfile: description: Whether the dockerfile or self have changed in any way value: ${{ steps.changed-files.outputs.dockerfile_any_changed || 'true' }} + devenv: + description: Whether the devenv or self have changed in any way + value: ${{ steps.changed-files.outputs.devenv_any_changed || 'true' }} runs: using: composite steps: @@ -136,6 +139,9 @@ runs: - '.vale.ini' - '.github/actions/change-detection/**' - '${{ inputs.self }}' + devenv: + - 'devenv/**' + - '${{ inputs.self }}' - name: Print all change groups shell: bash run: | @@ -157,3 +163,5 @@ runs: echo " --> ${{ steps.changed-files.outputs.docs_all_changed_files }}" echo "Dockerfile: ${{ steps.changed-files.outputs.dockerfile_any_changed || 'true' }}" echo " --> ${{ steps.changed-files.outputs.dockerfile_all_changed_files }}" + echo "devenv: ${{ steps.changed-files.outputs.devenv_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.devenv_all_changed_files }}" diff --git a/.github/renovate.json5 b/.github/renovate.json5 index aa33961bf95..4eb9f7b8ddf 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,6 +1,6 @@ { extends: ["config:recommended"], - enabledManagers: ["npm"], + enabledManagers: ["npm", "docker-compose"], ignorePresets: [ "github>grafana/grafana-renovate-config//presets/labels", ], @@ -26,7 +26,7 @@ "@types/slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate "@types/slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate ], - includePaths: ["package.json", "packages/**", "public/app/plugins/**"], + includePaths: ["package.json", "packages/**", "public/app/plugins/**", "devenv/frontend-service/docker-compose.yaml"], ignorePaths: ["emails/**", "**/mocks/**"], labels: ["area/frontend", "dependencies", "no-changelog"], postUpdateOptions: ["yarnDedupeHighest"], diff --git a/.github/workflows/backend-code-checks.yml b/.github/workflows/backend-code-checks.yml index 0294a121551..0367ee34641 100644 --- a/.github/workflows/backend-code-checks.yml +++ b/.github/workflows/backend-code-checks.yml @@ -79,7 +79,7 @@ jobs: 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 + for f in public/api-merged.json public/openapi3.json public/api-enterprise-spec.json; do git add $f; done if [ -z "$(git diff --name-only --cached)" ]; then echo "OpenAPI specs are up to date!" else diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index 9c6c0b32daa..1e4fa7790a6 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -18,6 +18,7 @@ jobs: contents: read outputs: changed: ${{ steps.detect-changes.outputs.frontend }} + devenv-changed: ${{ steps.detect-changes.outputs.devenv }} steps: - uses: actions/checkout@v5 with: @@ -169,3 +170,26 @@ jobs: needs: ${{ toJson(needs) }} failure-message: "One or more unit test jobs have failed" success-message: "All unit tests completed successfully" + + devenv: + needs: + - detect-changes + if: needs.detect-changes.outputs.devenv-changed == 'true' + runs-on: ubuntu-x64-large + name: "Devenv frontend-service build" + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Setup Docker + uses: docker/setup-docker-action@3fb92d6d9c634363128c8cce4bc3b2826526370a # v4 + - name: Setup Node.js + uses: ./.github/actions/setup-node + - name: Install Tilt + run: curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash + - name: Create empty config files # TODO: the tiltfile should conditionally mount these only if they exist, like the enterprise license + run: | + touch devenv/frontend-service/configs/grafana-api.local.ini + touch devenv/frontend-service/configs/frontend-service.local.ini + - name: Test frontend-service Tiltfile + run: tilt ci --file devenv/frontend-service/Tiltfile diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index 356f7a02b2b..b80d801b1b0 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -150,10 +150,16 @@ jobs: echo "✅ $full_pkg passed" fi done + # Set output for artifact upload + if [ $EXIT_CODE -ne 0 ]; then + echo "upload_artifacts=true" >> "$GITHUB_OUTPUT" + else + echo "upload_artifacts=false" >> "$GITHUB_OUTPUT" + fi exit $EXIT_CODE - name: Output test profiles and traces uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4 - if: (matrix.shard == 'profiled' && !cancelled()) + if: matrix.shard == 'profiled' && !cancelled() && steps.run-profiled-tests.outputs.upload_artifacts == 'true' with: name: integration-test-profiles-sqlite-nocgo-${{ github.run_number }} path: profiles/ diff --git a/.github/workflows/storybook-a11y.yml b/.github/workflows/storybook-a11y.yml index 5736c86d8bf..04d01151bae 100644 --- a/.github/workflows/storybook-a11y.yml +++ b/.github/workflows/storybook-a11y.yml @@ -34,7 +34,7 @@ jobs: id-token: write needs: detect-changes if: needs.detect-changes.outputs.changed == 'true' - name: "Run Storybook a11y tests" + name: "Run Storybook a11y tests (light theme)" steps: - uses: actions/checkout@v5 with: @@ -64,7 +64,7 @@ jobs: id-token: write needs: detect-changes if: needs.detect-changes.outputs.changed == 'true' - name: "Run Storybook a11y tests" + name: "Run Storybook a11y tests (dark theme)" steps: - uses: actions/checkout@v5 with: diff --git a/.golangci.yml b/.golangci.yml index 1fee8547139..33b2b9e8a19 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -105,6 +105,8 @@ linters: - '**/pkg/tsdb/graphite/**/*' - '**/pkg/tsdb/mysql/*' - '**/pkg/tsdb/mysql/**/*' + - '**/pkg/tsdb/opentsdb/*' + - '**/pkg/tsdb/opentsdb/**/*' - '**/pkg/tsdb/parca/*' - '**/pkg/tsdb/parca/**/*' - '**/pkg/tsdb/tempo/*' diff --git a/Dockerfile b/Dockerfile index 909281146e7..7cb65f4a05f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -95,6 +95,7 @@ COPY pkg/aggregator pkg/aggregator COPY apps/playlist apps/playlist COPY apps/plugins apps/plugins COPY apps/shorturl apps/shorturl +COPY apps/annotation apps/annotation COPY apps/correlations apps/correlations COPY apps/preferences apps/preferences COPY apps/provisioning apps/provisioning diff --git a/apps/advisor/Makefile b/apps/advisor/Makefile index 010b283b65a..7ea492797fe 100644 --- a/apps/advisor/Makefile +++ b/apps/advisor/Makefile @@ -15,3 +15,20 @@ generate: install-app-sdk update-app-sdk .PHONY: run run: @go run ./pkg/standalone/server.go --etcd-servers=http://127.0.0.1:22379 --secure-port 7445 + +.PHONY: create-checks +create-checks: + @echo "Creating plugin check..." + @curl -k -X POST https://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks \ + -H "Content-Type: application/json" \ + -d '{"kind":"Check","apiVersion":"advisor.grafana.app/v0alpha1","spec":{"data":{}},"metadata":{"generateName":"check-","labels":{"advisor.grafana.app/type":"plugin"},"namespace":"stacks-1"},"status":{"report":{"count":0,"failures":[]}}}' \ + && echo "Plugin check created successfully" + @echo "Creating datasource check..." + @curl -k -X POST https://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks \ + -H "Content-Type: application/json" \ + -d '{"kind":"Check","apiVersion":"advisor.grafana.app/v0alpha1","spec":{"data":{}},"metadata":{"generateName":"check-","labels":{"advisor.grafana.app/type":"datasource"},"namespace":"stacks-1"},"status":{"report":{"count":0,"failures":[]}}}' \ + && echo "Datasource check created successfully" + +delete-checks: + @curl -k -X DELETE https://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks \ + && echo "All checks deleted successfully" diff --git a/apps/advisor/README.md b/apps/advisor/README.md index aa48ef8e4e4..c7929d9db62 100644 --- a/apps/advisor/README.md +++ b/apps/advisor/README.md @@ -152,14 +152,3 @@ Check [`security_config_step.go`](./pkg/app/checks/configchecks/security_config_ ## Testing Create tests for your check and its steps to ensure they work as expected. Test both successful and failure scenarios. - -## Running the Standalone Mode - -To run the standalone mode, you can use the `make run` command. This will start the advisor app in standalone mode, which means it will not be running in a Kubernetes cluster. - -```bash -make etcd # Start etcd in a docker container -make run # Start the advisor app in standalone mode -``` - -This will start the advisor app on port 7445. You can then access the advisor app at `http://localhost:7445`. diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 4b7e830796b..834540f5f67 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -15,8 +15,6 @@ require ( github.com/stretchr/testify v1.11.1 k8s.io/apimachinery v0.34.1 k8s.io/apiserver v0.34.1 - k8s.io/client-go v0.34.1 - k8s.io/component-base v0.34.1 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 ) @@ -45,7 +43,6 @@ replace github.com/grafana/grafana/apps/plugins => ../plugins replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 require ( - cel.dev/expr v0.24.0 // indirect cloud.google.com/go/compute/metadata v0.7.0 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect @@ -58,7 +55,6 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect @@ -68,13 +64,13 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/at-wat/mqtt-go v0.19.4 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect - github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect github.com/aws/smithy-go v1.23.1 // indirect github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -89,9 +85,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect @@ -106,7 +100,6 @@ require ( github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gchaincl/sqlhooks v1.3.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect @@ -114,7 +107,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.2 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-ldap/ldap/v3 v3.4.4 // indirect - github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.0 // indirect @@ -149,7 +142,6 @@ require ( 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/cel-go v0.26.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -159,7 +151,7 @@ require ( github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-aws-sdk v1.2.0 // indirect + github.com/grafana/grafana-aws-sdk v1.3.0 // indirect github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect github.com/grafana/grafana/apps/plugins v0.0.0 // indirect github.com/grafana/grafana/apps/provisioning v0.0.0 // indirect @@ -169,7 +161,6 @@ require ( github.com/grafana/sqlds/v4 v4.2.7 // 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/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -184,7 +175,6 @@ require ( github.com/hashicorp/memberlist v0.5.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect github.com/jessevdk/go-flags v1.6.1 // indirect github.com/jmespath-community/go-jmespath v1.1.1 // indirect @@ -199,7 +189,6 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lestrrat-go/strftime v1.0.4 // 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 @@ -252,30 +241,20 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tetratelabs/wazero v1.8.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.17 // indirect + github.com/tjhop/slog-gokit v0.1.5 // indirect github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect @@ -294,8 +273,6 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.6.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.43.0 // indirect @@ -318,26 +295,23 @@ require ( google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // 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/mail.v2 v2.3.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/telebot.v3 v3.3.8 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.1 // indirect k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/client-go v0.34.1 // indirect + k8s.io/component-base v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kms v0.34.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.39.1 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // 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/v6 v6.3.0 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 657be70c25b..b6232c51680 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -131,7 +131,6 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= -github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -173,46 +172,44 @@ github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= -github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE= +github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= -github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0= -github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= +github.com/aws/aws-sdk-go-v2/config v1.31.10 h1:7LllDZAegXU3yk41mwM6KcPu0wmjKGQB1bg99bNdQm4= +github.com/aws/aws-sdk-go-v2/config v1.31.10/go.mod h1:Ge6gzXPjqu4v0oHvgAwvGzYcK921GU0hQM25WF/Kl+8= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 h1:gLD09eaJUdiszm7vd1btiQUYE0Hj+0I2b8AS+75z9AY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8/go.mod h1:4RW3oMPt1POR74qVOC4SbubxAwdP4pCT0nSw3jycOU4= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U= github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg= github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 h1:FTdEN9dtWPB0EOURNtDPmwGp6GGvMqRJCAihkSl/1No= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.4/go.mod h1:mYubxV9Ff42fZH4kexj43gFPhgc/LyC7KqvUKt1watc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 h1:I7ghctfGXrscr7r1Ga/mDqSJKm7Fkpl5Mwq79Z+rZqU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0/go.mod h1:Zo9id81XP6jbayIFWNuDpA6lMBWhsVy+3ou2jLa4JnA= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0= github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= -github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0= -github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0= @@ -269,14 +266,6 @@ github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= -github.com/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM= -github.com/blugelabs/bluge v0.2.2/go.mod h1:am1LU9jS8dZgWkRzkGLQN3757EgMs3upWrU2fdN9foE= -github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo= -github.com/blugelabs/bluge_segment_api v0.2.0/go.mod h1:95XA+ZXfRj/IXADm7gZ+iTcWOJPg5jQTY1EReIzl3LA= -github.com/blugelabs/ice v1.0.0 h1:um7wf9e6jbkTVCrOyQq3tKK43fBMOvLUYxbj3Qtc4eo= -github.com/blugelabs/ice v1.0.0/go.mod h1:gNfFPk5zM+yxJROhthxhVQYjpBO9amuxWXJQ2Lo+IbQ= -github.com/blugelabs/ice/v2 v2.0.1 h1:mzHbntLjk2v7eDRgoXCgzOsPKN1Tenu9Svo6l9cTLS4= -github.com/blugelabs/ice/v2 v2.0.1/go.mod h1:QxAWSPNwZwsIqS25c3lbIPFQrVvT1sphf5x5DfMLH5M= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= @@ -290,8 +279,6 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= -github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds= -github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -334,10 +321,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= -github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= @@ -360,8 +343,6 @@ github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9j github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= -github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8= -github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= @@ -456,16 +437,14 @@ github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXg github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= +github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= -github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7BdIXhsA= github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw= github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= @@ -663,16 +642,11 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae h1:NLPwY3tIP0lg0g9wTRiMcypm6VRXW6W+MOLBsq8JSVA= github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= @@ -689,8 +663,8 @@ github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+ github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c= github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= -github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw= -github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A= +github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= +github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc= github.com/grafana/grafana-plugin-sdk-go v0.281.0 h1:V8dGyatzcOLQeivFhBV2JWMwTSZH/clDnpfKG9p3dTA= @@ -816,8 +790,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= -github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -833,9 +805,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= @@ -882,8 +851,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4= github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0= -github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= @@ -1055,7 +1022,6 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/pressly/goose/v3 v3.25.0 h1:6WeYhMWGRCzpyd89SpODFnCBCKz41KrVbRT58nVjGng= github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= @@ -1073,7 +1039,6 @@ github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= @@ -1088,7 +1053,6 @@ github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57J github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -1115,9 +1079,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= @@ -1132,7 +1093,6 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg= github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1141,12 +1101,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1160,7 +1114,6 @@ github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= @@ -1186,10 +1139,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= @@ -1202,10 +1153,8 @@ github.com/thejerf/slogassert v0.3.4/go.mod h1:0zn9ISLVKo1aPMTqcGfG1o6dWwt+Rk574 github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE9IrvgBA2C1mRN1lc= github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= -github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc= +github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= @@ -1213,16 +1162,6 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= -github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= -github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= -github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= -github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= -github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= @@ -1233,8 +1172,6 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1259,12 +1196,6 @@ go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+ go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= -go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= -go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= -go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= -go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= -go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= -go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= @@ -1417,7 +1348,6 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1528,7 +1458,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1830,7 +1759,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1892,8 +1820,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/apps/advisor/pkg/app/app.go b/apps/advisor/pkg/app/app.go index 0e423957b82..d4019631ef6 100644 --- a/apps/advisor/pkg/app/app.go +++ b/apps/advisor/pkg/app/app.go @@ -20,6 +20,10 @@ import ( ) func New(cfg app.Config) (app.App, error) { + // Needed until https://github.com/grafana/grafana-app-sdk/pull/1077 + if cfg.KubeConfig.APIPath == "" { + cfg.KubeConfig.APIPath = "apis" + } // Read config specificConfig, ok := cfg.SpecificConfig.(checkregistry.AdvisorAppConfig) if !ok { diff --git a/apps/advisor/pkg/app/authorizer.go b/apps/advisor/pkg/app/authorizer.go index 4d216ee9edd..576773330e3 100644 --- a/apps/advisor/pkg/app/authorizer.go +++ b/apps/advisor/pkg/app/authorizer.go @@ -3,6 +3,7 @@ package app import ( "context" + claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -15,7 +16,22 @@ func GetAuthorizer() authorizer.Authorizer { return authorizer.DecisionNoOpinion, "", nil } - // require a user + // Check for service identity + if identity.IsServiceIdentity(ctx) { + return authorizer.DecisionAllow, "", nil + } + + // Check for access policy identity + info, ok := claims.AuthInfoFrom(ctx) + if ok && claims.IsIdentityType(info.GetIdentityType(), claims.TypeAccessPolicy) { + // For access policy identities, we need to use ResourceAuthorizer + // This requires an AccessClient, which should be provided by the API server + // For now, we'll use the default ResourceAuthorizer from the API server + // This will be set up by the API server's authorization chain + return authorizer.DecisionNoOpinion, "", nil + } + + // For regular Grafana users, check if they are admin u, err := identity.GetRequester(ctx) if err != nil { return authorizer.DecisionDeny, "valid user is required", err diff --git a/apps/advisor/pkg/app/authorizer_test.go b/apps/advisor/pkg/app/authorizer_test.go index 1384176969e..de8c0fad2db 100644 --- a/apps/advisor/pkg/app/authorizer_test.go +++ b/apps/advisor/pkg/app/authorizer_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/stretchr/testify/assert" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -79,4 +80,12 @@ func (m *mockUser) HasRole(role identity.RoleType) bool { return role == identity.RoleAdmin && m.isGrafanaAdmin } +func (m *mockUser) GetUID() string { + return "test-uid" +} + +func (m *mockUser) GetIdentityType() claims.IdentityType { + return claims.TypeUser +} + // Implement other methods of identity.Requester as needed diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go b/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go deleted file mode 100644 index b751b606cd8..00000000000 --- a/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go +++ /dev/null @@ -1,12 +0,0 @@ -package mockchecks - -import "github.com/grafana/grafana/apps/advisor/pkg/app/checks" - -// mockchecks.CheckRegistry is a mock implementation of the checkregistry.CheckService interface -// TODO: Add mocked checks here -type CheckRegistry struct { -} - -func (m *CheckRegistry) Checks() []checks.Check { - return []checks.Check{} -} diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index 524dae04268..cae29e181fd 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -26,7 +26,7 @@ const ( type check struct { DatasourceSvc datasources.DataSourceService PluginStore pluginstore.Store - PluginContextProvider pluginContextProvider + PluginContextProvider PluginContextProvider PluginClient plugins.Client PluginRepo repo.Service GrafanaVersion string @@ -37,7 +37,7 @@ type check struct { func New( datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store, - pluginContextProvider pluginContextProvider, + pluginContextProvider PluginContextProvider, pluginClient plugins.Client, pluginRepo repo.Service, grafanaVersion string, @@ -168,6 +168,6 @@ func (c *check) canBeInstalled(ctx context.Context, pluginType string) (bool, er return isAvailableInRepo, nil } -type pluginContextProvider interface { +type PluginContextProvider interface { GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) } diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go b/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go index c020d0ba82a..7e88e872e05 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go @@ -15,7 +15,7 @@ import ( ) type healthCheckStep struct { - PluginContextProvider pluginContextProvider + PluginContextProvider PluginContextProvider PluginClient plugins.Client } diff --git a/apps/advisor/pkg/standalone/server.go b/apps/advisor/pkg/standalone/server.go deleted file mode 100644 index 6e2ad32cc8b..00000000000 --- a/apps/advisor/pkg/standalone/server.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "log/slog" - "os" - - "k8s.io/apiserver/pkg/admission" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/client-go/rest" - "k8s.io/component-base/cli" - - "github.com/grafana/grafana-app-sdk/app" - "github.com/grafana/grafana-app-sdk/k8s/apiserver" - "github.com/grafana/grafana-app-sdk/k8s/apiserver/cmd/server" - "github.com/grafana/grafana-app-sdk/logging" - "github.com/grafana/grafana-app-sdk/simple" - "github.com/grafana/grafana/apps/advisor/pkg/apis" - advisorapp "github.com/grafana/grafana/apps/advisor/pkg/app" - "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry" - "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry/mockchecks" -) - -func main() { - logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: slog.LevelDebug, - })) - provider := simple.NewAppProvider(apis.LocalManifest(), nil, advisorapp.New) - config := app.Config{ - KubeConfig: rest.Config{}, // this will be replaced by the apiserver loopback config - ManifestData: *apis.LocalManifest().ManifestData, - SpecificConfig: checkregistry.AdvisorAppConfig{ - CheckRegistry: &mockchecks.CheckRegistry{}, - PluginConfig: map[string]string{}, - StackID: "1", // Numeric stack ID for standalone mode - OrgService: nil, // Not needed when StackID is set - }, - } - installer, err := apiserver.NewDefaultAppInstaller(provider, config, &apis.GoTypeAssociator{}) - if err != nil { - panic(err) - } - ctx := genericapiserver.SetupSignalContext() - opts := apiserver.NewOptions([]apiserver.AppInstaller{installer}) - opts.RecommendedOptions.Authentication = nil - opts.RecommendedOptions.Authorization = nil - opts.RecommendedOptions.CoreAPI = nil - opts.RecommendedOptions.EgressSelector = nil - opts.RecommendedOptions.Admission.Plugins = admission.NewPlugins() - opts.RecommendedOptions.Admission.RecommendedPluginOrder = []string{} - opts.RecommendedOptions.Admission.EnablePlugins = []string{} - opts.RecommendedOptions.Features.EnablePriorityAndFairness = false - opts.RecommendedOptions.ExtraAdmissionInitializers = func(_ *genericapiserver.RecommendedConfig) ([]admission.PluginInitializer, error) { - return nil, nil - } - cmd := server.NewCommandStartServer(ctx, opts) - code := cli.Run(cmd) - os.Exit(code) -} diff --git a/apps/alerting/rules/definitions/alerting-manifest.yaml b/apps/alerting/rules/definitions/alerting-manifest.yaml index 836bd2c9a94..a8927dd1ea4 100644 --- a/apps/alerting/rules/definitions/alerting-manifest.yaml +++ b/apps/alerting/rules/definitions/alerting-manifest.yaml @@ -8,7 +8,16 @@ spec: preferredVersion: v0alpha1 versions: - kinds: - - conversion: false + - admission: + mutation: + operations: + - CREATE + - UPDATE + validation: + operations: + - CREATE + - UPDATE + conversion: false kind: AlertRule plural: AlertRules schemas: @@ -214,7 +223,16 @@ spec: - spec.panelRef.dashboardUID - spec.panelRef.panelID - spec.notificationSettings.receiver - - conversion: false + - admission: + mutation: + operations: + - CREATE + - UPDATE + validation: + operations: + - CREATE + - UPDATE + conversion: false kind: RecordingRule plural: RecordingRules schemas: diff --git a/apps/alerting/rules/go.mod b/apps/alerting/rules/go.mod index f10d66be03f..f1ccc575706 100644 --- a/apps/alerting/rules/go.mod +++ b/apps/alerting/rules/go.mod @@ -5,6 +5,7 @@ go 1.25.3 require ( github.com/grafana/grafana-app-sdk v0.48.1 github.com/grafana/grafana-app-sdk/logging v0.48.1 + github.com/prometheus/common v0.67.1 k8s.io/apimachinery v0.34.1 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 ) @@ -49,7 +50,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect diff --git a/apps/alerting/rules/kinds/alertRule.cue b/apps/alerting/rules/kinds/alertRule.cue index bbc374c244c..f3c7293cf76 100644 --- a/apps/alerting/rules/kinds/alertRule.cue +++ b/apps/alerting/rules/kinds/alertRule.cue @@ -13,6 +13,18 @@ alertRulev0alpha1: alertRuleKind & { schema: { spec: v0alpha1.AlertRuleSpec } + validation: { + operations: [ + "CREATE", + "UPDATE", + ] + } + mutation: { + operations: [ + "CREATE", + "UPDATE", + ] + } selectableFields: [ "spec.title", "spec.paused", diff --git a/apps/alerting/rules/kinds/recordingRule.cue b/apps/alerting/rules/kinds/recordingRule.cue index 3a6e14907ba..3a126afc0f2 100644 --- a/apps/alerting/rules/kinds/recordingRule.cue +++ b/apps/alerting/rules/kinds/recordingRule.cue @@ -13,6 +13,18 @@ recordingRulev0alpha1: recordingRuleKind & { schema: { spec: v0alpha1.RecordingRuleSpec } + validation: { + operations: [ + "CREATE", + "UPDATE", + ] + } + mutation: { + operations: [ + "CREATE", + "UPDATE", + ] + } selectableFields: [ "spec.title", "spec.paused", diff --git a/apps/alerting/rules/pkg/apis/alerting/v0alpha1/alertrule_ext.go b/apps/alerting/rules/pkg/apis/alerting/v0alpha1/alertrule_ext.go index da4b07b3df8..a705925fd5f 100644 --- a/apps/alerting/rules/pkg/apis/alerting/v0alpha1/alertrule_ext.go +++ b/apps/alerting/rules/pkg/apis/alerting/v0alpha1/alertrule_ext.go @@ -3,6 +3,7 @@ package v0alpha1 import ( "fmt" "slices" + "time" ) func (o *AlertRule) GetProvenanceStatus() string { @@ -48,4 +49,78 @@ func (s *AlertRuleSpec) ExecErrStateOrDefault() string { return s.ExecErrState } -// TODO: add duration clamping for the field types AlertRulePromDuration, AlertRulePromDurationWMillis, and the For and KeepFiringFor string pointers +func (d *AlertRulePromDuration) ToDuration() (time.Duration, error) { + return ToDuration(string(*d)) +} + +func (d *AlertRulePromDurationWMillis) ToDuration() (time.Duration, error) { + return ToDuration(string(*d)) +} + +func (d *AlertRulePromDuration) Clamp() error { + clampedDuration, err := ClampDuration(string(*d)) + if err != nil { + return err + } + *d = AlertRulePromDuration(clampedDuration) + return nil +} + +func (d *AlertRulePromDurationWMillis) Clamp() error { + clampedDuration, err := ClampDuration(string(*d)) + if err != nil { + return err + } + *d = AlertRulePromDurationWMillis(clampedDuration) + return nil +} + +func (spec *AlertRuleSpec) ClampDurations() error { + // clamp all duration fields + if err := spec.Trigger.Interval.Clamp(); err != nil { + return err + } + if spec.For != nil { + clamped, err := ClampDuration(*spec.For) + if err != nil { + return err + } + spec.For = &clamped + } + if spec.KeepFiringFor != nil { + clamped, err := ClampDuration(*spec.KeepFiringFor) + if err != nil { + return err + } + spec.KeepFiringFor = &clamped + } + if spec.NotificationSettings != nil { + if spec.NotificationSettings.GroupWait != nil { + if err := spec.NotificationSettings.GroupWait.Clamp(); err != nil { + return err + } + } + if spec.NotificationSettings.GroupInterval != nil { + if err := spec.NotificationSettings.GroupInterval.Clamp(); err != nil { + return err + } + } + if spec.NotificationSettings.RepeatInterval != nil { + if err := spec.NotificationSettings.RepeatInterval.Clamp(); err != nil { + return err + } + } + } + for k, expr := range spec.Expressions { + if expr.RelativeTimeRange != nil { + if err := expr.RelativeTimeRange.From.Clamp(); err != nil { + return err + } + if err := expr.RelativeTimeRange.To.Clamp(); err != nil { + return err + } + spec.Expressions[k] = expr + } + } + return nil +} diff --git a/apps/alerting/rules/pkg/apis/alerting/v0alpha1/ext.go b/apps/alerting/rules/pkg/apis/alerting/v0alpha1/ext.go index 7b87f8ee8e9..f4efc73e014 100644 --- a/apps/alerting/rules/pkg/apis/alerting/v0alpha1/ext.go +++ b/apps/alerting/rules/pkg/apis/alerting/v0alpha1/ext.go @@ -1,17 +1,50 @@ package v0alpha1 +import ( + "fmt" + "time" + + prom_model "github.com/prometheus/common/model" +) + const ( InternalPrefix = "grafana.com/" GroupLabelKey = InternalPrefix + "group" GroupIndexLabelKey = GroupLabelKey + "-index" ProvenanceStatusAnnotationKey = InternalPrefix + "provenance" + // Copy of the max title length used in legacy validation path + AlertRuleMaxTitleLength = 190 + // Annotation key used to store the folder UID on resources + FolderAnnotationKey = "grafana.app/folder" + FolderLabelKey = FolderAnnotationKey ) +// NOTE: This is a copy of the constants from the alertrule package to avoid circular imports. +// Keep in sync with pkg/services/ngalert/models/provisioning.go const ( - ProvenanceStatusNone = "" - ProvenanceStatusAPI = "api" + ProvenanceStatusNone = "" + ProvenanceStatusAPI = "api" + ProvenanceStatusFile = "file" + ProvenanceStatusConvertedPrometheus = "converted_prometheus" ) var ( - AcceptedProvenanceStatuses = []string{ProvenanceStatusNone, ProvenanceStatusAPI} + AcceptedProvenanceStatuses = []string{ProvenanceStatusNone, ProvenanceStatusAPI, ProvenanceStatusFile, ProvenanceStatusConvertedPrometheus} ) + +func ToDuration(s string) (time.Duration, error) { + promDuration, err := prom_model.ParseDuration(s) + if err != nil { + return 0, fmt.Errorf("invalid duration format: %w", err) + } + return time.Duration(promDuration), nil +} + +// Convert the string duration to the longest valid Prometheus duration format (e.g., "60s" -> "1m") +func ClampDuration(s string) (string, error) { + promDuration, err := prom_model.ParseDuration(s) + if err != nil { + return "", fmt.Errorf("invalid duration format: %w", err) + } + return promDuration.String(), nil +} diff --git a/apps/alerting/rules/pkg/apis/alerting/v0alpha1/recordingrule_ext.go b/apps/alerting/rules/pkg/apis/alerting/v0alpha1/recordingrule_ext.go index 0d40fc4c7b6..50bd606ef39 100644 --- a/apps/alerting/rules/pkg/apis/alerting/v0alpha1/recordingrule_ext.go +++ b/apps/alerting/rules/pkg/apis/alerting/v0alpha1/recordingrule_ext.go @@ -3,6 +3,7 @@ package v0alpha1 import ( "fmt" "slices" + "time" ) func (o *RecordingRule) GetProvenanceStatus() string { @@ -27,4 +28,47 @@ func (o *RecordingRule) SetProvenanceStatus(status string) (err error) { return } -// TODO: add duration clamping for the field types RecordingRulePromDurationWMillis and RecordingRulePromDuration +func (d *RecordingRulePromDuration) ToDuration() (time.Duration, error) { + return ToDuration(string(*d)) +} + +func (d *RecordingRulePromDurationWMillis) ToDuration() (time.Duration, error) { + return ToDuration(string(*d)) +} + +func (d *RecordingRulePromDuration) Clamp() error { + clampedDuration, err := ClampDuration(string(*d)) + if err != nil { + return err + } + *d = RecordingRulePromDuration(clampedDuration) + return nil +} + +func (d *RecordingRulePromDurationWMillis) Clamp() error { + clampedDuration, err := ClampDuration(string(*d)) + if err != nil { + return err + } + *d = RecordingRulePromDurationWMillis(clampedDuration) + return nil +} + +func (spec *RecordingRuleSpec) ClampDurations() error { + // clamp all duration fields + if err := spec.Trigger.Interval.Clamp(); err != nil { + return err + } + for k, expr := range spec.Expressions { + if expr.RelativeTimeRange != nil { + if err := expr.RelativeTimeRange.From.Clamp(); err != nil { + return err + } + if err := expr.RelativeTimeRange.To.Clamp(); err != nil { + return err + } + spec.Expressions[k] = expr + } + } + return nil +} diff --git a/apps/alerting/rules/pkg/apis/alerting_manifest.go b/apps/alerting/rules/pkg/apis/alerting_manifest.go index 5057a58c6e6..f81fb7d43fc 100644 --- a/apps/alerting/rules/pkg/apis/alerting_manifest.go +++ b/apps/alerting/rules/pkg/apis/alerting_manifest.go @@ -42,7 +42,21 @@ var appManifestData = app.ManifestData{ Plural: "AlertRules", Scope: "Namespaced", Conversion: false, - Schema: &versionSchemaAlertRulev0alpha1, + Admission: &app.AdmissionCapabilities{ + Validation: &app.ValidationCapability{ + Operations: []app.AdmissionOperation{ + app.AdmissionOperationCreate, + app.AdmissionOperationUpdate, + }, + }, + Mutation: &app.MutationCapability{ + Operations: []app.AdmissionOperation{ + app.AdmissionOperationCreate, + app.AdmissionOperationUpdate, + }, + }, + }, + Schema: &versionSchemaAlertRulev0alpha1, SelectableFields: []string{ "spec.title", "spec.paused", @@ -57,7 +71,21 @@ var appManifestData = app.ManifestData{ Plural: "RecordingRules", Scope: "Namespaced", Conversion: false, - Schema: &versionSchemaRecordingRulev0alpha1, + Admission: &app.AdmissionCapabilities{ + Validation: &app.ValidationCapability{ + Operations: []app.AdmissionOperation{ + app.AdmissionOperationCreate, + app.AdmissionOperationUpdate, + }, + }, + Mutation: &app.MutationCapability{ + Operations: []app.AdmissionOperation{ + app.AdmissionOperationCreate, + app.AdmissionOperationUpdate, + }, + }, + }, + Schema: &versionSchemaRecordingRulev0alpha1, SelectableFields: []string{ "spec.title", "spec.paused", diff --git a/apps/alerting/rules/pkg/app/alertrule/mutator.go b/apps/alerting/rules/pkg/app/alertrule/mutator.go new file mode 100644 index 00000000000..a127e0699c9 --- /dev/null +++ b/apps/alerting/rules/pkg/app/alertrule/mutator.go @@ -0,0 +1,45 @@ +package alertrule + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/simple" + v1 "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/config" +) + +func NewMutator(cfg config.RuntimeConfig) *simple.Mutator { + return &simple.Mutator{ + MutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) { + // Mutate folder label to match folder UID from annotation + r, ok := req.Object.(*v1.AlertRule) + if !ok || r == nil { + // Nothing to do or wrong type; no mutation + return nil, nil + } + + // Read folder UID from annotation + folderUID := "" + if r.Annotations != nil { + folderUID = r.Annotations[v1.FolderAnnotationKey] + } + + // Ensure labels map exists and set the folder label if folderUID is present + if folderUID != "" { + if r.Labels == nil { + r.Labels = make(map[string]string) + } + // Maintain folder metadata label for downstream systems (alertmanager grouping etc.) + r.Labels[v1.FolderLabelKey] = folderUID + } + + // clamp all duration fields + if err := r.Spec.ClampDurations(); err != nil { + return nil, err + } + + return &app.MutatingResponse{UpdatedObject: r}, nil + }, + } +} diff --git a/apps/alerting/rules/pkg/app/alertrule/validator.go b/apps/alerting/rules/pkg/app/alertrule/validator.go new file mode 100644 index 00000000000..636c1b4a323 --- /dev/null +++ b/apps/alerting/rules/pkg/app/alertrule/validator.go @@ -0,0 +1,122 @@ +package alertrule + +import ( + "context" + "fmt" + "slices" + "time" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana-app-sdk/simple" + model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/config" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/util" + prom_model "github.com/prometheus/common/model" +) + +// validateGroupLabels now delegates to util.ValidateGroupLabels for shared logic. +func validateGroupLabels(r *model.AlertRule, oldObject resource.Object, action resource.AdmissionAction) error { + var oldLabels map[string]string + if oldObject != nil { + if oldRule, ok := oldObject.(*model.AlertRule); ok { + oldLabels = oldRule.Labels + } else { + return fmt.Errorf("old object is not of type *v0alpha1.AlertRule") + } + } + return util.ValidateGroupLabels(r.Labels, oldLabels, action) +} + +func NewValidator(cfg config.RuntimeConfig) *simple.Validator { + return &simple.Validator{ + ValidateFunc: func(ctx context.Context, req *app.AdmissionRequest) error { + // Cast to specific type + r, ok := req.Object.(*model.AlertRule) + if !ok { + return fmt.Errorf("object is not of type *v0alpha1.AlertRule") + } + // 1) Validate provenance status annotation + sourceProv := r.GetProvenanceStatus() + if !slices.Contains(model.AcceptedProvenanceStatuses, sourceProv) { + return fmt.Errorf("invalid provenance status: %s", sourceProv) + } + + // 2) Validate group labels rules + if err := validateGroupLabels(r, req.OldObject, req.Action); err != nil { + return err + } + + // 3) Validate folder is set and exists + // Read folder UID directly from annotations + folderUID := "" + if r.Annotations != nil { + folderUID = r.Annotations[model.FolderAnnotationKey] + } + if folderUID == "" { + return fmt.Errorf("folder is required") + } + if cfg.FolderValidator != nil { + ok, verr := cfg.FolderValidator(ctx, folderUID) + if verr != nil { + return fmt.Errorf("failed to validate folder: %w", verr) + } + if !ok { + return fmt.Errorf("folder does not exist: %s", folderUID) + } + } + + // 4) Validate notification settings receiver if provided + if r.Spec.NotificationSettings != nil && r.Spec.NotificationSettings.Receiver != "" && cfg.NotificationSettingsValidator != nil { + ok, nerr := cfg.NotificationSettingsValidator(ctx, r.Spec.NotificationSettings.Receiver) + if nerr != nil { + return fmt.Errorf("failed to validate notification settings: %w", nerr) + } + if !ok { + return fmt.Errorf("invalid notification receiver: %s", r.Spec.NotificationSettings.Receiver) + } + } + + // 5) Enforce max title length + if len(r.Spec.Title) > model.AlertRuleMaxTitleLength { + return fmt.Errorf("alert rule title is too long. Max length is %d", model.AlertRuleMaxTitleLength) + } + + // 6) Validate evaluation interval against base interval + if err := util.ValidateInterval(cfg.BaseEvaluationInterval, &r.Spec.Trigger.Interval); err != nil { + return err + } + + // 7) Disallow reserved/spec system label keys + if r.Spec.Labels != nil { + for key := range r.Spec.Labels { + if _, bad := cfg.ReservedLabelKeys[key]; bad { + return fmt.Errorf("label key is reserved and cannot be specified: %s", key) + } + } + } + + // 8) For and KeepFiringFor must be >= 0 if set + if r.Spec.For != nil { + d, err := prom_model.ParseDuration(*r.Spec.For) + if err != nil { + return fmt.Errorf("invalid 'for' duration: %w", err) + } + if time.Duration(d) < 0 { + return fmt.Errorf("'for' cannot be less than 0") + } + } + if r.Spec.KeepFiringFor != nil { + d, err := prom_model.ParseDuration(*r.Spec.KeepFiringFor) + if err != nil { + return fmt.Errorf("invalid 'keepFiringFor' duration: %w", err) + } + if time.Duration(d) < 0 { + return fmt.Errorf("'keepFiringFor' cannot be less than 0") + } + } + + return nil + }, + } +} diff --git a/apps/alerting/rules/pkg/app/app.go b/apps/alerting/rules/pkg/app/app.go index ef0ab1883cf..3c546239c81 100644 --- a/apps/alerting/rules/pkg/app/app.go +++ b/apps/alerting/rules/pkg/app/app.go @@ -6,16 +6,29 @@ import ( "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/operator" + "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana-app-sdk/simple" "github.com/grafana/grafana/apps/alerting/rules/pkg/apis" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/alertrule" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/config" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/recordingrule" ) func New(cfg app.Config) (app.App, error) { managedKinds := make([]simple.AppManagedKind, 0) + runtimeCfg, ok := cfg.SpecificConfig.(config.RuntimeConfig) + if !ok { + return nil, config.ErrInvalidRuntimeConfig + } for _, kinds := range apis.GetKinds() { for _, kind := range kinds { - managedKinds = append(managedKinds, simple.AppManagedKind{Kind: kind}) + managedKind := simple.AppManagedKind{ + Kind: kind, + Validator: buildKindValidator(kind, runtimeCfg), + Mutator: buildKindMutator(kind, runtimeCfg), + } + managedKinds = append(managedKinds, managedKind) } } @@ -44,3 +57,23 @@ func New(cfg app.Config) (app.App, error) { return a, nil } + +func buildKindValidator(kind resource.Kind, cfg config.RuntimeConfig) *simple.Validator { + switch kind.Kind() { + case "AlertRule": + return alertrule.NewValidator(cfg) + case "RecordingRule": + return recordingrule.NewValidator(cfg) + } + return nil +} + +func buildKindMutator(kind resource.Kind, cfg config.RuntimeConfig) *simple.Mutator { + switch kind.Kind() { + case "AlertRule": + return alertrule.NewMutator(cfg) + case "RecordingRule": + return recordingrule.NewMutator(cfg) + } + return nil +} diff --git a/apps/alerting/rules/pkg/app/app_test.go b/apps/alerting/rules/pkg/app/app_test.go new file mode 100644 index 00000000000..405e397f8cd --- /dev/null +++ b/apps/alerting/rules/pkg/app/app_test.go @@ -0,0 +1,175 @@ +package app_test + +import ( + "context" + "testing" + "time" + + appsdk "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + + v1 "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/alertrule" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/config" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/recordingrule" +) + +func makeDefaultRuntimeConfig() config.RuntimeConfig { + return config.RuntimeConfig{ + FolderValidator: func(ctx context.Context, folderUID string) (bool, error) { return folderUID == "f1", nil }, + BaseEvaluationInterval: 60 * time.Second, // seconds + ReservedLabelKeys: map[string]struct{}{"__reserved__": {}, "grafana_folder": {}}, + NotificationSettingsValidator: func(ctx context.Context, receiver string) (bool, error) { return receiver == "notif-ok", nil }, + } +} + +func TestAlertRuleValidation_Success(t *testing.T) { + r := &v1.AlertRule{} + r.SetGroupVersionKind(v1.AlertRuleKind().GroupVersionKind()) + r.Name = "uid-1" + r.Namespace = "ns1" + r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"} + r.Labels = map[string]string{} + r.Spec = v1.AlertRuleSpec{ + Title: "ok", + Trigger: v1.AlertRuleIntervalTrigger{Interval: v1.AlertRulePromDuration("60s")}, + Expressions: v1.AlertRuleExpressionMap{"A": v1.AlertRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}}, + NoDataState: v1.DefaultNoDataState, + ExecErrState: v1.DefaultExecErrState, + NotificationSettings: &v1.AlertRuleV0alpha1SpecNotificationSettings{Receiver: "notif-ok"}, + } + + req := &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r} + validator := alertrule.NewValidator(makeDefaultRuntimeConfig()) + if err := validator.Validate(context.Background(), req); err != nil { + t.Fatalf("expected success, got error: %v", err) + } +} + +func TestAlertRuleValidation_Errors(t *testing.T) { + mk := func(mut func(r *v1.AlertRule)) error { + r := baseAlertRule() + mut(r) + return alertrule.NewValidator(makeDefaultRuntimeConfig()).Validate(context.Background(), &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r}) + } + + if err := mk(func(r *v1.AlertRule) { r.Annotations = nil }); err == nil { + t.Errorf("want folder required error") + } + if err := mk(func(r *v1.AlertRule) { r.Annotations[v1.FolderAnnotationKey] = "bad" }); err == nil { + t.Errorf("want folder not exist error") + } + if err := mk(func(r *v1.AlertRule) { r.Spec.Trigger.Interval = v1.AlertRulePromDuration("30s") }); err == nil { + t.Errorf("want base interval multiple error") + } + if err := mk(func(r *v1.AlertRule) { + r.Spec.NotificationSettings = &v1.AlertRuleV0alpha1SpecNotificationSettings{Receiver: "bad"} + }); err == nil { + t.Errorf("want invalid receiver error") + } + if err := mk(func(r *v1.AlertRule) { r.Labels[v1.GroupLabelKey] = "grp" }); err == nil { + t.Errorf("want group set on create error") + } + if err := mk(func(r *v1.AlertRule) { r.Spec.For = strPtr("-10s") }); err == nil { + t.Errorf("want for>=0 error") + } + if err := mk(func(r *v1.AlertRule) { + if r.Spec.Labels == nil { + r.Spec.Labels = map[string]v1.AlertRuleTemplateString{} + } + r.Spec.Labels["__reserved__"] = v1.AlertRuleTemplateString("x") + }); err == nil { + t.Errorf("want reserved label key error") + } +} + +func baseAlertRule() *v1.AlertRule { + r := &v1.AlertRule{} + r.SetGroupVersionKind(v1.AlertRuleKind().GroupVersionKind()) + r.Name = "uid-1" + r.Namespace = "ns1" + r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"} + r.Labels = map[string]string{} + r.Spec = v1.AlertRuleSpec{ + Title: "ok", + Trigger: v1.AlertRuleIntervalTrigger{Interval: v1.AlertRulePromDuration("60s")}, + Expressions: v1.AlertRuleExpressionMap{"A": v1.AlertRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}}, + NoDataState: v1.DefaultNoDataState, + ExecErrState: v1.DefaultExecErrState, + } + return r +} + +func TestRecordingRuleValidation_Success(t *testing.T) { + r := &v1.RecordingRule{} + r.SetGroupVersionKind(v1.RecordingRuleKind().GroupVersionKind()) + r.Name = "uid-2" + r.Namespace = "ns1" + r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"} + r.Labels = map[string]string{} + r.Spec = v1.RecordingRuleSpec{ + Title: "ok", + Trigger: v1.RecordingRuleIntervalTrigger{Interval: v1.RecordingRulePromDuration("60s")}, + Expressions: v1.RecordingRuleExpressionMap{"A": v1.RecordingRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}}, + Metric: "test_metric", + TargetDatasourceUID: "ds1", + } + + req := &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r} + validator := recordingrule.NewValidator(makeDefaultRuntimeConfig()) + if err := validator.Validate(context.Background(), req); err != nil { + t.Fatalf("expected success, got error: %v", err) + } +} + +func TestRecordingRuleValidation_Errors(t *testing.T) { + mk := func(mut func(r *v1.RecordingRule)) error { + r := baseRecordingRule() + mut(r) + return recordingrule.NewValidator(makeDefaultRuntimeConfig()).Validate(context.Background(), &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r}) + } + + if err := mk(func(r *v1.RecordingRule) { r.Annotations = nil }); err == nil { + t.Errorf("want folder required error") + } + if err := mk(func(r *v1.RecordingRule) { r.Annotations[v1.FolderAnnotationKey] = "bad" }); err == nil { + t.Errorf("want folder not exist error") + } + if err := mk(func(r *v1.RecordingRule) { r.Spec.Trigger.Interval = v1.RecordingRulePromDuration("30s") }); err == nil { + t.Errorf("want base interval multiple error") + } + if err := mk(func(r *v1.RecordingRule) { r.Labels[v1.GroupLabelKey] = "grp" }); err == nil { + t.Errorf("want group set on create error") + } + if err := mk(func(r *v1.RecordingRule) { r.Spec.Metric = "" }); err == nil { + t.Errorf("want metric required error") + } + if err := mk(func(r *v1.RecordingRule) { + if r.Spec.Labels == nil { + r.Spec.Labels = map[string]v1.RecordingRuleTemplateString{} + } + r.Spec.Labels["__reserved__"] = v1.RecordingRuleTemplateString("x") + }); err == nil { + t.Errorf("want reserved label key error") + } +} + +func baseRecordingRule() *v1.RecordingRule { + r := &v1.RecordingRule{} + r.SetGroupVersionKind(v1.RecordingRuleKind().GroupVersionKind()) + r.Name = "uid-1" + r.Namespace = "ns1" + r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"} + r.Labels = map[string]string{} + r.Spec = v1.RecordingRuleSpec{ + Title: "ok", + Trigger: v1.RecordingRuleIntervalTrigger{Interval: v1.RecordingRulePromDuration("60s")}, + Expressions: v1.RecordingRuleExpressionMap{"A": v1.RecordingRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}}, + Metric: "test_metric", + TargetDatasourceUID: "ds1", + } + return r +} + +func boolPtr(b bool) *bool { return &b } +func strPtr(s string) *string { return &s } diff --git a/apps/alerting/rules/pkg/app/config/runtime.go b/apps/alerting/rules/pkg/app/config/runtime.go new file mode 100644 index 00000000000..60936925a15 --- /dev/null +++ b/apps/alerting/rules/pkg/app/config/runtime.go @@ -0,0 +1,22 @@ +package config + +import ( + "context" + "errors" + "time" +) + +var ( + ErrInvalidRuntimeConfig = errors.New("invalid runtime config provided to alerting/rules app") +) + +// RuntimeConfig holds configuration values needed at runtime by the alerting/rules app from the running Grafana instance. +type RuntimeConfig struct { + // function to check folder existence given its uid + FolderValidator func(ctx context.Context, folderUID string) (bool, error) + // base evaluation interval + BaseEvaluationInterval time.Duration + // set of strings which are illegal for label keys on rules + ReservedLabelKeys map[string]struct{} + NotificationSettingsValidator func(ctx context.Context, receiver string) (bool, error) +} diff --git a/apps/alerting/rules/pkg/app/recordingrule/mutator.go b/apps/alerting/rules/pkg/app/recordingrule/mutator.go new file mode 100644 index 00000000000..31378c553f0 --- /dev/null +++ b/apps/alerting/rules/pkg/app/recordingrule/mutator.go @@ -0,0 +1,37 @@ +package recordingrule + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/simple" + v1 "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/config" +) + +func NewMutator(cfg config.RuntimeConfig) *simple.Mutator { + return &simple.Mutator{ + MutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) { + r, ok := req.Object.(*v1.RecordingRule) + if !ok || r == nil { + return nil, nil + } + + folderUID := "" + if r.Annotations != nil { + folderUID = r.Annotations[v1.FolderAnnotationKey] + } + + if folderUID != "" { + if r.Labels == nil { + r.Labels = make(map[string]string) + } + r.Labels[v1.FolderLabelKey] = folderUID + } + if err := r.Spec.ClampDurations(); err != nil { + return nil, err + } + return &app.MutatingResponse{UpdatedObject: r}, nil + }, + } +} diff --git a/apps/alerting/rules/pkg/app/recordingrule/validator.go b/apps/alerting/rules/pkg/app/recordingrule/validator.go new file mode 100644 index 00000000000..ea9e8f207d6 --- /dev/null +++ b/apps/alerting/rules/pkg/app/recordingrule/validator.go @@ -0,0 +1,95 @@ +package recordingrule + +import ( + "context" + "fmt" + "slices" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana-app-sdk/simple" + model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/config" + "github.com/grafana/grafana/apps/alerting/rules/pkg/app/util" + prom_model "github.com/prometheus/common/model" +) + +// validateGroupLabels now delegates to util.ValidateGroupLabels for shared logic. +func validateGroupLabels(r *model.RecordingRule, oldObject resource.Object, action resource.AdmissionAction) error { + var oldLabels map[string]string + if oldObject != nil { + if oldRule, ok := oldObject.(*model.RecordingRule); ok { + oldLabels = oldRule.Labels + } else { + return fmt.Errorf("old object is not of type *v0alpha1.RecordingRule") + } + } + return util.ValidateGroupLabels(r.Labels, oldLabels, action) +} + +func NewValidator(cfg config.RuntimeConfig) *simple.Validator { + return &simple.Validator{ + ValidateFunc: func(ctx context.Context, req *app.AdmissionRequest) error { + // Cast to specific type + r, ok := req.Object.(*model.RecordingRule) + if !ok { + return fmt.Errorf("object is not of type *v0alpha1.RecordingRule") + } + + sourceProv := r.GetProvenanceStatus() + if !slices.Contains(model.AcceptedProvenanceStatuses, sourceProv) { + return fmt.Errorf("invalid provenance status: %s", sourceProv) + } + + if err := validateGroupLabels(r, req.OldObject, req.Action); err != nil { + return err + } + + folderUID := "" + if r.Annotations != nil { + folderUID = r.Annotations[model.FolderAnnotationKey] + } + if folderUID == "" { + return fmt.Errorf("folder is required") + } + if cfg.FolderValidator != nil { + ok, verr := cfg.FolderValidator(ctx, folderUID) + if verr != nil { + return fmt.Errorf("failed to validate folder: %w", verr) + } + if !ok { + return fmt.Errorf("folder does not exist: %s", folderUID) + } + } + + if len(r.Spec.Title) > model.AlertRuleMaxTitleLength { + return fmt.Errorf("recording rule title is too long. Max length is %d", model.AlertRuleMaxTitleLength) + } + + if err := util.ValidateInterval(cfg.BaseEvaluationInterval, &r.Spec.Trigger.Interval); err != nil { + return err + } + + if r.Spec.Labels != nil { + for key := range r.Spec.Labels { + if _, bad := cfg.ReservedLabelKeys[key]; bad { + return fmt.Errorf("label key is reserved and cannot be specified: %s", key) + } + } + } + + if r.Spec.Metric == "" { + return fmt.Errorf("metric must be specified") + } + metric := prom_model.LabelValue(r.Spec.Metric) + if !metric.IsValid() { + return fmt.Errorf("metric contains invalid characters") + } + if !prom_model.IsValidMetricName(metric) { // nolint:staticcheck + return fmt.Errorf("invalid metric name") + } + + return nil + }, + } +} diff --git a/apps/alerting/rules/pkg/app/util/group_validation.go b/apps/alerting/rules/pkg/app/util/group_validation.go new file mode 100644 index 00000000000..8624ef816e8 --- /dev/null +++ b/apps/alerting/rules/pkg/app/util/group_validation.go @@ -0,0 +1,59 @@ +package util + +import ( + "fmt" + "strconv" + + "github.com/grafana/grafana-app-sdk/resource" + model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" +) + +// ValidateGroupLabels enforces the cross-field rules for group-related labels. +// +// Rules enforced: +// - On create, group-related labels must not be set. +// - If one of group or group-index is set, the other must also be set. +// - group-index must be an integer. +// - On update, group/group-index can only be present if they were present on the old object. +// +// Pass current labels and, when available, the previous object's labels. The previous labels +// may be nil when there is no old object (e.g., create operations). +func ValidateGroupLabels(labels map[string]string, oldLabels map[string]string, action resource.AdmissionAction) error { + groupStr, groupExists := labels[model.GroupLabelKey] + groupIndexStr, groupIndexStrExists := labels[model.GroupIndexLabelKey] + + if groupExists || groupIndexStrExists { + if action == resource.AdmissionActionCreate { + return fmt.Errorf("cannot set group when creating a new rule") + } + if groupExists && !groupIndexStrExists { + return fmt.Errorf("%s must be set when %s is set", model.GroupIndexLabelKey, model.GroupLabelKey) + } + if groupIndexStrExists && !groupExists { + return fmt.Errorf("%s must be set when %s is set", model.GroupLabelKey, model.GroupIndexLabelKey) + } + // Disallow empty values when labels are present + if groupExists && groupStr == "" { + return fmt.Errorf("%s cannot be empty", model.GroupLabelKey) + } + if groupIndexStrExists && groupIndexStr == "" { + return fmt.Errorf("%s cannot be empty", model.GroupIndexLabelKey) + } + if _, err := strconv.Atoi(groupIndexStr); err != nil { + return fmt.Errorf("invalid %s: %w", model.GroupIndexLabelKey, err) + } + + // On updates, ensure that group and group-index are only set if the old object had them set + if oldLabels != nil { + _, oldGroupExists := oldLabels[model.GroupLabelKey] + _, oldGroupIndexExists := oldLabels[model.GroupIndexLabelKey] + if groupExists && !oldGroupExists { + return fmt.Errorf("cannot set group when updating un-grouped rule") + } + if groupIndexStrExists && !oldGroupIndexExists { + return fmt.Errorf("cannot set group-index when updating un-grouped rule") + } + } + } + return nil +} diff --git a/apps/alerting/rules/pkg/app/util/group_validation_test.go b/apps/alerting/rules/pkg/app/util/group_validation_test.go new file mode 100644 index 00000000000..c2b44980b93 --- /dev/null +++ b/apps/alerting/rules/pkg/app/util/group_validation_test.go @@ -0,0 +1,109 @@ +package util + +import ( + "testing" + + "github.com/grafana/grafana-app-sdk/resource" + model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" +) + +func TestValidateGroupLabels(t *testing.T) { + group := model.GroupLabelKey + groupIdx := model.GroupIndexLabelKey + + tests := []struct { + name string + labels map[string]string + oldLabels map[string]string + action resource.AdmissionAction + wantErr bool + }{ + { + name: "update empty group value", + labels: map[string]string{group: "", groupIdx: "1"}, + action: resource.AdmissionActionUpdate, + wantErr: true, + }, + { + name: "update empty group-index value", + labels: map[string]string{group: "g1", groupIdx: ""}, + action: resource.AdmissionActionUpdate, + wantErr: true, + }, + { + name: "create empty group value disallowed", + labels: map[string]string{group: ""}, + action: resource.AdmissionActionCreate, + wantErr: true, + }, + { + name: "create no labels allowed", + labels: nil, + action: resource.AdmissionActionCreate, + wantErr: false, + }, + { + name: "create with group disallowed", + labels: map[string]string{group: "g1"}, + action: resource.AdmissionActionCreate, + wantErr: true, + }, + { + name: "create with group-index disallowed", + labels: map[string]string{groupIdx: "1"}, + action: resource.AdmissionActionCreate, + wantErr: true, + }, + { + name: "update missing paired index", + labels: map[string]string{group: "g1"}, + action: resource.AdmissionActionUpdate, + wantErr: true, + }, + { + name: "update missing paired group", + labels: map[string]string{groupIdx: "1"}, + action: resource.AdmissionActionUpdate, + wantErr: true, + }, + { + name: "update invalid index format", + labels: map[string]string{group: "g1", groupIdx: "x"}, + oldLabels: map[string]string{group: "g1", groupIdx: "0"}, + action: resource.AdmissionActionUpdate, + wantErr: true, + }, + { + name: "update cannot add group when previously ungrouped", + labels: map[string]string{group: "g1", groupIdx: "0"}, + oldLabels: map[string]string{}, + action: resource.AdmissionActionUpdate, + wantErr: true, + }, + { + name: "update allowed when previously grouped", + labels: map[string]string{group: "g1", groupIdx: "2"}, + oldLabels: map[string]string{group: "g1", groupIdx: "1"}, + action: resource.AdmissionActionUpdate, + wantErr: false, + }, + { + name: "update no labels remains allowed", + labels: nil, + action: resource.AdmissionActionUpdate, + wantErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := ValidateGroupLabels(tc.labels, tc.oldLabels, tc.action) + if tc.wantErr && err == nil { + t.Fatalf("expected error, got nil") + } + if !tc.wantErr && err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} diff --git a/apps/alerting/rules/pkg/app/util/validator.go b/apps/alerting/rules/pkg/app/util/validator.go new file mode 100644 index 00000000000..5f09c61e1cc --- /dev/null +++ b/apps/alerting/rules/pkg/app/util/validator.go @@ -0,0 +1,27 @@ +package util + +import ( + "fmt" + "time" +) + +type DurationLike interface { + ToDuration() (time.Duration, error) +} + +func ValidateInterval(baseInterval time.Duration, d DurationLike) error { + interval, err := d.ToDuration() + if err != nil { + return fmt.Errorf("invalid trigger interval: %w", err) + } + // Ensure interval is positive and an integer multiple of BaseEvaluationInterval (if provided) + if interval <= 0 { + return fmt.Errorf("trigger interval must be greater than 0") + } + if baseInterval > 0 { + if (interval % baseInterval) != 0 { + return fmt.Errorf("trigger interval must be a multiple of base evaluation interval (%s)", baseInterval.String()) + } + } + return nil +} diff --git a/apps/annotation/Makefile b/apps/annotation/Makefile new file mode 100644 index 00000000000..230bfd4149a --- /dev/null +++ b/apps/annotation/Makefile @@ -0,0 +1,9 @@ +include ../sdk.mk + +.PHONY: generate # Run Grafana App SDK code generation +generate: install-app-sdk update-app-sdk + @$(APP_SDK_BIN) generate \ + --source=./kinds/ \ + --gogenpath=./pkg/apis \ + --grouping=group \ + --defencoding=none \ No newline at end of file diff --git a/apps/annotation/go.mod b/apps/annotation/go.mod new file mode 100644 index 00000000000..351d54e1cbd --- /dev/null +++ b/apps/annotation/go.mod @@ -0,0 +1,93 @@ +module github.com/grafana/grafana/apps/annotation + +go 1.24.0 + +require ( + github.com/grafana/grafana-app-sdk v0.48.1 + github.com/grafana/grafana-app-sdk/logging v0.48.1 + k8s.io/apimachinery v0.34.1 + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/getkin/kin-openapi v0.133.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.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-test/deep v1.1.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.7.0 // 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/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/client-go v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // 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/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/apps/annotation/go.sum b/apps/annotation/go.sum new file mode 100644 index 00000000000..a0e59cd5731 --- /dev/null +++ b/apps/annotation/go.sum @@ -0,0 +1,240 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+fRS+8zkek= +github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c= +github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= +github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= +github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= +github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/apps/annotation/kinds/annotation.cue b/apps/annotation/kinds/annotation.cue new file mode 100644 index 00000000000..5df0fe9f12a --- /dev/null +++ b/apps/annotation/kinds/annotation.cue @@ -0,0 +1,17 @@ +package kinds + +annotationv0alpha1: { + kind: "Annotation" + pluralName: "Annotations" + schema: { + spec: { + text: string + time: int64 + timeEnd?: int64 + dashboardUID?: string + panelID?: int64 + tags?: [...string] + } + } +} + diff --git a/apps/annotation/kinds/cue.mod/module.cue b/apps/annotation/kinds/cue.mod/module.cue new file mode 100644 index 00000000000..a7f0a62536f --- /dev/null +++ b/apps/annotation/kinds/cue.mod/module.cue @@ -0,0 +1,2 @@ +module: "github.com/grafana/grafana/apps/annotation/kinds" +language: version: "v0.8.2" diff --git a/apps/annotation/kinds/manifest.cue b/apps/annotation/kinds/manifest.cue new file mode 100644 index 00000000000..b41931fc0c2 --- /dev/null +++ b/apps/annotation/kinds/manifest.cue @@ -0,0 +1,21 @@ +package kinds + +manifest: { + appName: "annotation" + groupOverride: "annotation.grafana.app" + versions: { + "v0alpha1": v0alpha1 + } +} + +v0alpha1: { + kinds: [annotationv0alpha1] + codegen: { + ts: { + enabled: true + } + go: { + enabled: true + } + } +} diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_client_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_client_gen.go new file mode 100644 index 00000000000..c3f9cc986cc --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_client_gen.go @@ -0,0 +1,99 @@ +package v0alpha1 + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type AnnotationClient struct { + client *resource.TypedClient[*Annotation, *AnnotationList] +} + +func NewAnnotationClient(client resource.Client) *AnnotationClient { + return &AnnotationClient{ + client: resource.NewTypedClient[*Annotation, *AnnotationList](client, AnnotationKind()), + } +} + +func NewAnnotationClientFromGenerator(generator resource.ClientGenerator) (*AnnotationClient, error) { + c, err := generator.ClientFor(AnnotationKind()) + if err != nil { + return nil, err + } + return NewAnnotationClient(c), nil +} + +func (c *AnnotationClient) Get(ctx context.Context, identifier resource.Identifier) (*Annotation, error) { + return c.client.Get(ctx, identifier) +} + +func (c *AnnotationClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*AnnotationList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *AnnotationClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*AnnotationList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *AnnotationClient) Create(ctx context.Context, obj *Annotation, opts resource.CreateOptions) (*Annotation, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = AnnotationKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *AnnotationClient) Update(ctx context.Context, obj *Annotation, opts resource.UpdateOptions) (*Annotation, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *AnnotationClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Annotation, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *AnnotationClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus AnnotationStatus, opts resource.UpdateOptions) (*Annotation, error) { + return c.client.Update(ctx, &Annotation{ + TypeMeta: metav1.TypeMeta{ + Kind: AnnotationKind().Kind(), + APIVersion: GroupVersion.Identifier(), + }, + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, + }, + Status: newStatus, + }, resource.UpdateOptions{ + Subresource: "status", + ResourceVersion: opts.ResourceVersion, + }) +} + +func (c *AnnotationClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_codec_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_codec_gen.go new file mode 100644 index 00000000000..3d754151d55 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// AnnotationJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type AnnotationJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*AnnotationJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*AnnotationJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &AnnotationJSONCodec{} diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_metadata_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_metadata_gen.go new file mode 100644 index 00000000000..d35800b9804 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type AnnotationMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewAnnotationMetadata creates a new AnnotationMetadata object. +func NewAnnotationMetadata() *AnnotationMetadata { + return &AnnotationMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_object_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_object_gen.go new file mode 100644 index 00000000000..db99bcffa08 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_object_gen.go @@ -0,0 +1,319 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type Annotation struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the Annotation + Spec AnnotationSpec `json:"spec" yaml:"spec"` + + Status AnnotationStatus `json:"status" yaml:"status"` +} + +func (o *Annotation) GetSpec() any { + return o.Spec +} + +func (o *Annotation) SetSpec(spec any) error { + cast, ok := spec.(AnnotationSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *Annotation) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *Annotation) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *Annotation) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(AnnotationStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type AnnotationStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *Annotation) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *Annotation) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *Annotation) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *Annotation) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *Annotation) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *Annotation) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *Annotation) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *Annotation) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *Annotation) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *Annotation) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *Annotation) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *Annotation) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *Annotation) DeepCopy() *Annotation { + cpy := &Annotation{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *Annotation) DeepCopyInto(dst *Annotation) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &Annotation{} + +// +k8s:openapi-gen=true +type AnnotationList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []Annotation `json:"items" yaml:"items"` +} + +func (o *AnnotationList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *AnnotationList) Copy() resource.ListObject { + cpy := &AnnotationList{ + TypeMeta: o.TypeMeta, + Items: make([]Annotation, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*Annotation); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *AnnotationList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *AnnotationList) SetItems(items []resource.Object) { + o.Items = make([]Annotation, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*Annotation) + } +} + +func (o *AnnotationList) DeepCopy() *AnnotationList { + cpy := &AnnotationList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *AnnotationList) DeepCopyInto(dst *AnnotationList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &AnnotationList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *AnnotationSpec) DeepCopy() *AnnotationSpec { + cpy := &AnnotationSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *AnnotationSpec) DeepCopyInto(dst *AnnotationSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of AnnotationStatus +func (s *AnnotationStatus) DeepCopy() *AnnotationStatus { + cpy := &AnnotationStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies AnnotationStatus into another AnnotationStatus object +func (s *AnnotationStatus) DeepCopyInto(dst *AnnotationStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_schema_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_schema_gen.go new file mode 100644 index 00000000000..3127e6a8954 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaAnnotation = resource.NewSimpleSchema("annotation.grafana.app", "v0alpha1", &Annotation{}, &AnnotationList{}, resource.WithKind("Annotation"), + resource.WithPlural("annotations"), resource.WithScope(resource.NamespacedScope)) + kindAnnotation = resource.Kind{ + Schema: schemaAnnotation, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &AnnotationJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func AnnotationKind() resource.Kind { + return kindAnnotation +} + +// Schema returns a resource.SimpleSchema representation of Annotation +func AnnotationSchema() *resource.SimpleSchema { + return schemaAnnotation +} + +// Interface compliance checks +var _ resource.Schema = kindAnnotation diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_spec_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_spec_gen.go new file mode 100644 index 00000000000..f748c6b4253 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_spec_gen.go @@ -0,0 +1,18 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type AnnotationSpec struct { + Text string `json:"text"` + Time int64 `json:"time"` + TimeEnd *int64 `json:"timeEnd,omitempty"` + DashboardUID *string `json:"dashboardUID,omitempty"` + PanelID *int64 `json:"panelID,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +// NewAnnotationSpec creates a new AnnotationSpec object. +func NewAnnotationSpec() *AnnotationSpec { + return &AnnotationSpec{} +} diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_status_gen.go b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_status_gen.go new file mode 100644 index 00000000000..f681750af88 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/annotation_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type AnnotationstatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State AnnotationStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewAnnotationstatusOperatorState creates a new AnnotationstatusOperatorState object. +func NewAnnotationstatusOperatorState() *AnnotationstatusOperatorState { + return &AnnotationstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type AnnotationStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]AnnotationstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewAnnotationStatus creates a new AnnotationStatus object. +func NewAnnotationStatus() *AnnotationStatus { + return &AnnotationStatus{} +} + +// +k8s:openapi-gen=true +type AnnotationStatusOperatorStateState string + +const ( + AnnotationStatusOperatorStateStateSuccess AnnotationStatusOperatorStateState = "success" + AnnotationStatusOperatorStateStateInProgress AnnotationStatusOperatorStateState = "in_progress" + AnnotationStatusOperatorStateStateFailed AnnotationStatusOperatorStateState = "failed" +) diff --git a/apps/annotation/pkg/apis/annotation/v0alpha1/constants.go b/apps/annotation/pkg/apis/annotation/v0alpha1/constants.go new file mode 100644 index 00000000000..e28dcb0720d --- /dev/null +++ b/apps/annotation/pkg/apis/annotation/v0alpha1/constants.go @@ -0,0 +1,18 @@ +package v0alpha1 + +import "k8s.io/apimachinery/pkg/runtime/schema" + +const ( + // APIGroup is the API group used by all kinds in this package + APIGroup = "annotation.grafana.app" + // APIVersion is the API version used by all kinds in this package + APIVersion = "v0alpha1" +) + +var ( + // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package + GroupVersion = schema.GroupVersion{ + Group: APIGroup, + Version: APIVersion, + } +) diff --git a/apps/annotation/pkg/apis/annotation_manifest.go b/apps/annotation/pkg/apis/annotation_manifest.go new file mode 100644 index 00000000000..5e7d6ae8b07 --- /dev/null +++ b/apps/annotation/pkg/apis/annotation_manifest.go @@ -0,0 +1,124 @@ +// +// This file is generated by grafana-app-sdk +// DO NOT EDIT +// + +package apis + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" + + v0alpha1 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1" +) + +var ( + rawSchemaAnnotationv0alpha1 = []byte(`{"Annotation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"dashboardUID":{"type":"string"},"panelID":{"type":"integer"},"tags":{"items":{"type":"string"},"type":"array"},"text":{"type":"string"},"time":{"type":"integer"},"timeEnd":{"type":"integer"}},"required":["text","time"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaAnnotationv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaAnnotationv0alpha1, &versionSchemaAnnotationv0alpha1) +) + +var appManifestData = app.ManifestData{ + AppName: "annotation", + Group: "annotation.grafana.app", + PreferredVersion: "v0alpha1", + Versions: []app.ManifestVersion{ + { + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ + { + Kind: "Annotation", + Plural: "Annotations", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaAnnotationv0alpha1, + }, + }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + Schemas: map[string]spec.Schema{}, + }, + }, + }, +} + +func LocalManifest() app.Manifest { + return app.NewEmbeddedManifest(appManifestData) +} + +func RemoteManifest() app.Manifest { + return app.NewAPIServerManifest("annotation") +} + +var kindVersionToGoType = map[string]resource.Kind{ + "Annotation/v0alpha1": v0alpha1.AnnotationKind(), +} + +// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. +// If there is no association for the provided Kind and Version, exists will return false. +func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) { + goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] + return goType, exists +} + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +// Resource routes (those without a kind) should prefix their route with "/" if the route is namespaced (otherwise the route is assumed to be cluster-scope) +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +var customRouteToGoParamsType = map[string]runtime.Object{} + +func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +var customRouteToGoRequestBodyType = map[string]any{} + +func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +type GoTypeAssociator struct{} + +func NewGoTypeAssociator() *GoTypeAssociator { + return &GoTypeAssociator{} +} + +func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { + return ManifestGoTypeAssociator(kind, version) +} +func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteResponsesAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { + return ManifestCustomRouteQueryAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) +} diff --git a/apps/annotation/pkg/app/app.go b/apps/annotation/pkg/app/app.go new file mode 100644 index 00000000000..d1665c0e776 --- /dev/null +++ b/apps/annotation/pkg/app/app.go @@ -0,0 +1,54 @@ +package app + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-app-sdk/operator" + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana-app-sdk/simple" + "k8s.io/apimachinery/pkg/runtime/schema" + + annotationv0alpha1 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1" +) + +func New(cfg app.Config) (app.App, error) { + simpleConfig := simple.AppConfig{ + Name: "annotation", + KubeConfig: cfg.KubeConfig, + InformerConfig: simple.AppInformerConfig{ + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + logging.FromContext(ctx).Error("Informer processing error", "error", err) + }, + }, + }, + ManagedKinds: []simple.AppManagedKind{{ + Kind: annotationv0alpha1.AnnotationKind(), + }, + }, + } + + a, err := simple.NewApp(simpleConfig) + if err != nil { + return nil, err + } + + err = a.ValidateManifest(cfg.ManifestData) + if err != nil { + return nil, err + } + + return a, nil +} + +func GetKinds() map[schema.GroupVersion][]resource.Kind { + gv := schema.GroupVersion{ + Group: annotationv0alpha1.AnnotationKind().Group(), + Version: annotationv0alpha1.AnnotationKind().Version(), + } + return map[schema.GroupVersion][]resource.Kind{ + gv: {annotationv0alpha1.AnnotationKind()}, + } +} diff --git a/apps/annotation/plugin/src/generated/annotation/v0alpha1/annotation_object_gen.ts b/apps/annotation/plugin/src/generated/annotation/v0alpha1/annotation_object_gen.ts new file mode 100644 index 00000000000..2f7316bd74b --- /dev/null +++ b/apps/annotation/plugin/src/generated/annotation/v0alpha1/annotation_object_gen.ts @@ -0,0 +1,49 @@ +/* + * This file was generated by grafana-app-sdk. DO NOT EDIT. + */ +import { Spec } from './types.spec.gen'; +import { Status } from './types.status.gen'; + +export interface Metadata { + name: string; + namespace: string; + generateName?: string; + selfLink?: string; + uid?: string; + resourceVersion?: string; + generation?: number; + creationTimestamp?: string; + deletionTimestamp?: string; + deletionGracePeriodSeconds?: number; + labels?: Record; + annotations?: Record; + ownerReferences?: OwnerReference[]; + finalizers?: string[]; + managedFields?: ManagedFieldsEntry[]; +} + +export interface OwnerReference { + apiVersion: string; + kind: string; + name: string; + uid: string; + controller?: boolean; + blockOwnerDeletion?: boolean; +} + +export interface ManagedFieldsEntry { + manager?: string; + operation?: string; + apiVersion?: string; + time?: string; + fieldsType?: string; + subresource?: string; +} + +export interface Annotation { + kind: string; + apiVersion: string; + metadata: Metadata; + spec: Spec; + status: Status; +} diff --git a/apps/shorturl/plugin/src/generated/shorturl/v1alpha1/types.metadata.gen.ts b/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.metadata.gen.ts similarity index 100% rename from apps/shorturl/plugin/src/generated/shorturl/v1alpha1/types.metadata.gen.ts rename to apps/annotation/plugin/src/generated/annotation/v0alpha1/types.metadata.gen.ts diff --git a/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.spec.gen.ts b/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.spec.gen.ts new file mode 100644 index 00000000000..e35814f387e --- /dev/null +++ b/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.spec.gen.ts @@ -0,0 +1,16 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +export interface Spec { + text: string; + time: number; + timeEnd?: number; + dashboardUID?: string; + panelID?: number; + tags?: string[]; +} + +export const defaultSpec = (): Spec => ({ + text: "", + time: 0, +}); + diff --git a/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.status.gen.ts b/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.status.gen.ts new file mode 100644 index 00000000000..01be8df7961 --- /dev/null +++ b/apps/annotation/plugin/src/generated/annotation/v0alpha1/types.status.gen.ts @@ -0,0 +1,30 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +export interface OperatorState { + // lastEvaluation is the ResourceVersion last evaluated + lastEvaluation: string; + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + state: "success" | "in_progress" | "failed"; + // descriptiveState is an optional more descriptive state field which has no requirements on format + descriptiveState?: string; + // details contains any extra information that is operator-specific + details?: Record; +} + +export const defaultOperatorState = (): OperatorState => ({ + lastEvaluation: "", + state: "success", +}); + +export interface Status { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + operatorStates?: Record; + // additionalFields is reserved for future use + additionalFields?: Record; +} + +export const defaultStatus = (): Status => ({ +}); + diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 6bad69b84a9..5bca05229db 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -154,6 +154,8 @@ FieldConfigSource: { defaults: FieldConfig // Overrides are the options applied to specific fields overriding the defaults. overrides: [...{ + // Describes config override rules created when interacting with Grafana. + "__systemRef"?: string matcher: MatcherConfig properties: [...DynamicConfigValue] }] @@ -247,7 +249,8 @@ MatcherConfig: { } Threshold: { - value: number + // Value null means -Infinity + value: number | null color: string } diff --git a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue index 36c98d8f89e..ab6a67d54c7 100644 --- a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue @@ -156,6 +156,8 @@ FieldConfigSource: { defaults: FieldConfig // Overrides are the options applied to specific fields overriding the defaults. overrides: [...{ + // Describes config override rules created when interacting with Grafana. + "__systemRef"?: string matcher: MatcherConfig properties: [...DynamicConfigValue] }] @@ -249,7 +251,8 @@ MatcherConfig: { } Threshold: { - value: number + // Value null means -Infinity + value: number | null color: string } @@ -475,7 +478,7 @@ DataQueryKind: { PanelQuerySpec: { query: DataQueryKind - refId: string + refId: string | *"A" hidden: bool } @@ -876,7 +879,7 @@ IntervalVariableSpec: { auto: bool | *false auto_min: string | *"" auto_count: int | *0 - refresh: VariableRefresh + refresh: "onTimeRangeChanged" label?: string hide: VariableHide skipUrlSync: bool | *false diff --git a/apps/dashboard/pkg/apis/dashboard/types.go b/apps/dashboard/pkg/apis/dashboard/types.go index 29d6f8458e3..e204bee7f37 100644 --- a/apps/dashboard/pkg/apis/dashboard/types.go +++ b/apps/dashboard/pkg/apis/dashboard/types.go @@ -3,8 +3,9 @@ package dashboard // Information about how the requesting user can use a given dashboard type DashboardAccess struct { // Metadata fields - Slug string `json:"slug,omitempty"` - Url string `json:"url,omitempty"` + Slug string `json:"slug,omitempty"` + Url string `json:"url,omitempty"` + IsPublic bool `json:"isPublic"` // The permissions part CanSave bool `json:"canSave"` diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go index 7f813a12a1f..d0b222ac7d2 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go @@ -12,8 +12,9 @@ type DashboardWithAccessInfo struct { // +k8s:deepcopy-gen=true type DashboardAccess struct { // Metadata fields - Slug string `json:"slug,omitempty"` - Url string `json:"url,omitempty"` + Slug string `json:"slug,omitempty"` + Url string `json:"url,omitempty"` + IsPublic bool `json:"isPublic"` // The permissions part CanSave bool `json:"canSave"` diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go index 626201ec314..48869b1c0e8 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.conversion.go @@ -112,6 +112,7 @@ func Convert_dashboard_AnnotationPermission_To_v0alpha1_AnnotationPermission(in func autoConvert_v0alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin @@ -129,6 +130,7 @@ func Convert_v0alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *Dashboard func autoConvert_dashboard_DashboardAccess_To_v0alpha1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go index 1e81a87f2fa..b4a69d0031d 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go @@ -170,6 +170,13 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardAccess(ref common.ReferenceCall Format: "", }, }, + "isPublic": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, "canSave": { SchemaProps: spec.SchemaProps{ Description: "The permissions part", @@ -212,7 +219,7 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardAccess(ref common.ReferenceCall }, }, }, - Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, + Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, }, }, Dependencies: []string{ diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/types.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/types.go index ee4125d8f6a..285c3661980 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/types.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/types.go @@ -123,8 +123,9 @@ type DashboardWithAccessInfo struct { // +k8s:deepcopy-gen=true type DashboardAccess struct { // Metadata fields - Slug string `json:"slug,omitempty"` - Url string `json:"url,omitempty"` + Slug string `json:"slug,omitempty"` + Url string `json:"url,omitempty"` + IsPublic bool `json:"isPublic"` // The permissions part CanSave bool `json:"canSave"` diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go index e3f1f46785b..b8f76f5deb9 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go @@ -118,6 +118,7 @@ func Convert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission(in * func autoConvert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin @@ -135,6 +136,7 @@ func Convert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardA func autoConvert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go index be747be2ae3..3e2bdeab5f8 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go @@ -165,6 +165,13 @@ func schema_pkg_apis_dashboard_v1beta1_DashboardAccess(ref common.ReferenceCallb Format: "", }, }, + "isPublic": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, "canSave": { SchemaProps: spec.SchemaProps{ Description: "The permissions part", @@ -207,7 +214,7 @@ func schema_pkg_apis_dashboard_v1beta1_DashboardAccess(ref common.ReferenceCallb }, }, }, - Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, + Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, }, }, Dependencies: []string{ diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue index 602f639f81a..76e0921eb88 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue @@ -158,6 +158,8 @@ FieldConfigSource: { defaults: FieldConfig // Overrides are the options applied to specific fields overriding the defaults. overrides: [...{ + // Describes config override rules created when interacting with Grafana. + "__systemRef"?: string matcher: MatcherConfig properties: [...DynamicConfigValue] }] @@ -251,7 +253,8 @@ MatcherConfig: { } Threshold: { - value: number + // Value null means -Infinity + value: number | null color: string } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 5562379e68b..37d9ae8897f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -551,8 +551,9 @@ const ( // +k8s:openapi-gen=true type DashboardThreshold struct { - Value float64 `json:"value"` - Color string `json:"color"` + // Value null means -Infinity + Value *float64 `json:"value"` + Color string `json:"color"` } // NewDashboardThreshold creates a new DashboardThreshold object. @@ -1865,6 +1866,8 @@ func NewDashboardSpec() *DashboardSpec { // +k8s:openapi-gen=true type DashboardV2alpha1FieldConfigSourceOverrides struct { + // Describes config override rules created when interacting with Grafana. + SystemRef *string `json:"__systemRef,omitempty"` Matcher DashboardMatcherConfig `json:"matcher"` Properties []DashboardDynamicConfigValue `json:"properties"` } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/types.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/types.go index d3599063b7a..c48e2d00602 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/types.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/types.go @@ -123,8 +123,9 @@ type DashboardWithAccessInfo struct { // +k8s:deepcopy-gen=true type DashboardAccess struct { // Metadata fields - Slug string `json:"slug,omitempty"` - Url string `json:"url,omitempty"` + Slug string `json:"slug,omitempty"` + Url string `json:"url,omitempty"` + IsPublic bool `json:"isPublic"` // The permissions part CanSave bool `json:"canSave"` diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go index fe4c7479194..0ac74fef54e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.conversion.go @@ -118,6 +118,7 @@ func Convert_dashboard_AnnotationPermission_To_v2alpha1_AnnotationPermission(in func autoConvert_v2alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin @@ -135,6 +136,7 @@ func Convert_v2alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *Dashboard func autoConvert_dashboard_DashboardAccess_To_v2alpha1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index 548a8c93268..a167aa6d36e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -265,6 +265,13 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAccess(ref common.ReferenceCall Format: "", }, }, + "isPublic": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, "canSave": { SchemaProps: spec.SchemaProps{ Description: "The permissions part", @@ -307,7 +314,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAccess(ref common.ReferenceCall }, }, }, - Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, + Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, }, }, Dependencies: []string{ @@ -4423,9 +4430,9 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardThreshold(ref common.ReferenceC Properties: map[string]spec.Schema{ "value": { SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"number"}, - Format: "double", + Description: "Value null means -Infinity", + Type: []string{"number"}, + Format: "double", }, }, "color": { @@ -4670,6 +4677,13 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverri SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ + "__systemRef": { + SchemaProps: spec.SchemaProps{ + Description: "Describes config override rules created when interacting with Grafana.", + Type: []string{"string"}, + Format: "", + }, + }, "matcher": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list index a0691b0d9f4..25c9bf58f1c 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list @@ -67,6 +67,7 @@ API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/ap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardStringOrArrayOfString,String API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardStringOrFloat64,Float64 API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardStringOrFloat64,String +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardV2alpha1FieldConfigSourceOverrides,SystemRef API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RangeMap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RegexMap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,SpecialValueMap diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue index 5b0d436cba0..d9df0dd3ee3 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue @@ -160,6 +160,8 @@ FieldConfigSource: { defaults: FieldConfig // Overrides are the options applied to specific fields overriding the defaults. overrides: [...{ + // Describes config override rules created when interacting with Grafana. + "__systemRef"?: string matcher: MatcherConfig properties: [...DynamicConfigValue] }] @@ -253,7 +255,8 @@ MatcherConfig: { } Threshold: { - value: number + // Value null means -Infinity + value: number | null color: string } @@ -479,7 +482,7 @@ DataQueryKind: { PanelQuerySpec: { query: DataQueryKind - refId: string + refId: string | *"A" hidden: bool } @@ -880,7 +883,7 @@ IntervalVariableSpec: { auto: bool | *false auto_min: string | *"" auto_count: int | *0 - refresh: VariableRefresh + refresh: "onTimeRangeChanged" label?: string hide: VariableHide skipUrlSync: bool | *false diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go index 8dd81034e3b..fc0a8fcf26f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go @@ -209,6 +209,7 @@ type DashboardPanelQuerySpec struct { func NewDashboardPanelQuerySpec() *DashboardPanelQuerySpec { return &DashboardPanelQuerySpec{ Query: *NewDashboardDataQueryKind(), + RefId: "A", } } @@ -554,8 +555,9 @@ const ( // +k8s:openapi-gen=true type DashboardThreshold struct { - Value float64 `json:"value"` - Color string `json:"color"` + // Value null means -Infinity + Value *float64 `json:"value"` + Color string `json:"color"` } // NewDashboardThreshold creates a new DashboardThreshold object. @@ -1615,7 +1617,7 @@ type DashboardIntervalVariableSpec struct { Auto bool `json:"auto"` AutoMin string `json:"auto_min"` AutoCount int64 `json:"auto_count"` - Refresh DashboardVariableRefresh `json:"refresh"` + Refresh string `json:"refresh"` Label *string `json:"label,omitempty"` Hide DashboardVariableHide `json:"hide"` SkipUrlSync bool `json:"skipUrlSync"` @@ -1639,7 +1641,7 @@ func NewDashboardIntervalVariableSpec() *DashboardIntervalVariableSpec { Auto: false, AutoMin: "", AutoCount: 0, - Refresh: DashboardVariableRefreshNever, + Refresh: "onTimeRangeChanged", Hide: DashboardVariableHideDontHide, SkipUrlSync: false, } @@ -1926,6 +1928,8 @@ func NewDashboardV2beta1DataQueryKindDatasource() *DashboardV2beta1DataQueryKind // +k8s:openapi-gen=true type DashboardV2beta1FieldConfigSourceOverrides struct { + // Describes config override rules created when interacting with Grafana. + SystemRef *string `json:"__systemRef,omitempty"` Matcher DashboardMatcherConfig `json:"matcher"` Properties []DashboardDynamicConfigValue `json:"properties"` } diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/types.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/types.go index 8ecda3cfd7d..07a0cf6da4f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/types.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/types.go @@ -123,8 +123,9 @@ type DashboardWithAccessInfo struct { // +k8s:deepcopy-gen=true type DashboardAccess struct { // Metadata fields - Slug string `json:"slug,omitempty"` - Url string `json:"url,omitempty"` + Slug string `json:"slug,omitempty"` + Url string `json:"url,omitempty"` + IsPublic bool `json:"isPublic"` // The permissions part CanSave bool `json:"canSave"` diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.conversion.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.conversion.go index fce43178b8a..9d7ab0b1ee2 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.conversion.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.conversion.go @@ -118,6 +118,7 @@ func Convert_dashboard_AnnotationPermission_To_v2beta1_AnnotationPermission(in * func autoConvert_v2beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin @@ -135,6 +136,7 @@ func Convert_v2beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardA func autoConvert_dashboard_DashboardAccess_To_v2beta1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { out.Slug = in.Slug out.Url = in.Url + out.IsPublic = in.IsPublic out.CanSave = in.CanSave out.CanEdit = in.CanEdit out.CanAdmin = in.CanAdmin diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go index f8108887a27..f833f1ddee0 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go @@ -269,6 +269,13 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardAccess(ref common.ReferenceCallb Format: "", }, }, + "isPublic": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, "canSave": { SchemaProps: spec.SchemaProps{ Description: "The permissions part", @@ -311,7 +318,7 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardAccess(ref common.ReferenceCallb }, }, }, - Required: []string{"canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, + Required: []string{"isPublic", "canSave", "canEdit", "canAdmin", "canStar", "canDelete", "annotationsPermissions"}, }, }, Dependencies: []string{ @@ -4542,9 +4549,9 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardThreshold(ref common.ReferenceCa Properties: map[string]spec.Schema{ "value": { SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"number"}, - Format: "double", + Description: "Value null means -Infinity", + Type: []string{"number"}, + Format: "double", }, }, "color": { @@ -4825,6 +4832,13 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1FieldConfigSourceOverride SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ + "__systemRef": { + SchemaProps: spec.SchemaProps{ + Description: "Describes config override rules created when interacting with Grafana.", + Type: []string{"string"}, + Format: "", + }, + }, "matcher": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi_violation_exceptions.list index 5a0edf5747d..3007e07c806 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi_violation_exceptions.list @@ -68,6 +68,7 @@ API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/ap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardStringOrArrayOfString,String API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardStringOrFloat64,Float64 API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardStringOrFloat64,String +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardV2beta1FieldConfigSourceOverrides,SystemRef API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RangeMap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RegexMap API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,SpecialValueMap diff --git a/apps/dashboard/pkg/migration/README.md b/apps/dashboard/pkg/migration/README.md index 4dffadf42bb..422038d6128 100644 --- a/apps/dashboard/pkg/migration/README.md +++ b/apps/dashboard/pkg/migration/README.md @@ -198,7 +198,7 @@ grafana_dashboard_migration_conversion_failure_total{ ### Error Types -The `error_type` label classifies failures into three categories: +The `error_type` label classifies failures into four categories: #### 1. `conversion_error` - General conversion failures not related to schema migration @@ -215,6 +215,12 @@ The `error_type` label classifies failures into three categories: - These are logged as warnings rather than errors - Indicates dashboards that cannot be migrated automatically +#### 4. `conversion_data_loss_error` +- Data loss detected during conversion +- Automatically checks that panels, queries, annotations, and links are preserved +- Triggered when target has fewer items than source +- Includes detailed loss metrics in logs (see [Data Loss Detection](#data-loss-detection)) + ### Logging #### Log structure @@ -236,6 +242,13 @@ All migration logs use structured logging with consistent field names: - `erroredConversionFunc` - Name of the conversion function that failed - `error` - The actual error message +**Data Loss Fields (conversion_data_loss_error only):** +- `panelsLost` - Number of panels lost +- `queriesLost` - Number of queries lost +- `annotationsLost` - Number of annotations lost +- `linksLost` - Number of links lost +- `variablesLost` - Number of template variables lost + #### Log levels ##### Success (DEBUG level) @@ -285,6 +298,55 @@ All migration logs use structured logging with consistent field names: } ``` +##### Data Loss Error (ERROR level) +```json +{ + "level": "error", + "msg": "Dashboard conversion failed", + "sourceVersionAPI": "dashboard.grafana.app/v1beta1", + "targetVersionAPI": "dashboard.grafana.app/v2alpha1", + "erroredConversionFunc": "V1beta1_to_V2alpha1", + "dashboardUID": "abc123", + "sourceSchemaVersion": 42, + "targetSchemaVersion": 42, + "panelsLost": 0, + "queriesLost": 2, + "annotationsLost": 0, + "linksLost": 0, + "variablesLost": 0, + "errorType": "conversion_data_loss_error", + "error": "data loss detected: query count decreased from 7 to 5" +} +``` + +### Data Loss Detection + +**Automatic Runtime Checks:** + +Every conversion automatically detects data loss by comparing: +- **Panel count** - Visualization panels (regular + library panels) +- **Query count** - Data source queries (excludes invalid row panel queries) +- **Annotation count** - Dashboard-level annotations +- **Link count** - Navigation links +- **Variable count** - Template variables (from `templating.list` in v0/v1, `variables` in v2) + +**Detection Logic:** +- ✅ **Allows additions**: Default annotations, enriched data +- ❌ **Detects losses**: Any decrease in counts triggers `conversion_data_loss_error` + +**Testing:** + +Run comprehensive data loss tests on all conversion test files: + +```bash +# Test all conversions for data loss +go test ./apps/dashboard/pkg/migration/conversion/... -run TestDataLossDetectionOnAllInputFiles -v + +# Test shows detailed panel/query analysis when loss is detected +``` + +**Implementation:** See `conversion/conversion_data_loss_detection.go` and `conversion/README.md` for details. + ### Implementation Details #### Automatic instrumentation @@ -293,6 +355,7 @@ All dashboard conversions are automatically instrumented via the `withConversion ```go // All conversion functions are wrapped automatically +// Includes metrics, logging, and data loss detection s.AddConversionFunc((*dashv0.Dashboard)(nil), (*dashv1.Dashboard)(nil), withConversionMetrics(dashv0.APIVERSION, dashv1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { return Convert_V0_to_V1(a.(*dashv0.Dashboard), b.(*dashv1.Dashboard), scope) @@ -319,6 +382,15 @@ type ConversionError struct { currentAPIVersion string targetAPIVersion string } + +// Data loss errors are detected when dashboard components (panels, queries, annotations, links, variables) +// are lost during conversion +type ConversionDataLossError struct { + functionName string // Function where data loss was detected (e.g., "V1_to_V2alpha1") + message string // Detailed error message with loss statistics + sourceAPIVersion string // Source API version (e.g., "dashboard.grafana.app/v1beta1") + targetAPIVersion string // Target API version (e.g., "dashboard.grafana.app/v2alpha1") +} ``` ### Registration diff --git a/apps/dashboard/pkg/migration/conversion/conversion.go b/apps/dashboard/pkg/migration/conversion/conversion.go index e38986c200c..90795b7779a 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion.go +++ b/apps/dashboard/pkg/migration/conversion/conversion.go @@ -8,25 +8,26 @@ 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/apps/dashboard/pkg/migration/schemaversion" ) -func RegisterConversions(s *runtime.Scheme) error { +func RegisterConversions(s *runtime.Scheme, dsInfoProvider schemaversion.DataSourceInfoProvider) error { // v0 conversions if err := s.AddConversionFunc((*dashv0.Dashboard)(nil), (*dashv1.Dashboard)(nil), withConversionMetrics(dashv0.APIVERSION, dashv1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V0_to_V1(a.(*dashv0.Dashboard), b.(*dashv1.Dashboard), scope) + return Convert_V0_to_V1beta1(a.(*dashv0.Dashboard), b.(*dashv1.Dashboard), scope) })); err != nil { return err } if err := s.AddConversionFunc((*dashv0.Dashboard)(nil), (*dashv2alpha1.Dashboard)(nil), withConversionMetrics(dashv0.APIVERSION, dashv2alpha1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V0_to_V2alpha1(a.(*dashv0.Dashboard), b.(*dashv2alpha1.Dashboard), scope) + return Convert_V0_to_V2alpha1(a.(*dashv0.Dashboard), b.(*dashv2alpha1.Dashboard), scope, dsInfoProvider) })); err != nil { return err } if err := s.AddConversionFunc((*dashv0.Dashboard)(nil), (*dashv2beta1.Dashboard)(nil), withConversionMetrics(dashv0.APIVERSION, dashv2beta1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V0_to_V2beta1(a.(*dashv0.Dashboard), b.(*dashv2beta1.Dashboard), scope) + return Convert_V0_to_V2beta1(a.(*dashv0.Dashboard), b.(*dashv2beta1.Dashboard), scope, dsInfoProvider) })); err != nil { return err } @@ -34,19 +35,19 @@ func RegisterConversions(s *runtime.Scheme) error { // v1 conversions if err := s.AddConversionFunc((*dashv1.Dashboard)(nil), (*dashv0.Dashboard)(nil), withConversionMetrics(dashv1.APIVERSION, dashv0.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V1_to_V0(a.(*dashv1.Dashboard), b.(*dashv0.Dashboard), scope) + return Convert_V1beta1_to_V0(a.(*dashv1.Dashboard), b.(*dashv0.Dashboard), scope) })); err != nil { return err } if err := s.AddConversionFunc((*dashv1.Dashboard)(nil), (*dashv2alpha1.Dashboard)(nil), withConversionMetrics(dashv1.APIVERSION, dashv2alpha1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V1_to_V2alpha1(a.(*dashv1.Dashboard), b.(*dashv2alpha1.Dashboard), scope) + return Convert_V1beta1_to_V2alpha1(a.(*dashv1.Dashboard), b.(*dashv2alpha1.Dashboard), scope, dsInfoProvider) })); err != nil { return err } if err := s.AddConversionFunc((*dashv1.Dashboard)(nil), (*dashv2beta1.Dashboard)(nil), withConversionMetrics(dashv1.APIVERSION, dashv2beta1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V1_to_V2beta1(a.(*dashv1.Dashboard), b.(*dashv2beta1.Dashboard), scope) + return Convert_V1beta1_to_V2beta1(a.(*dashv1.Dashboard), b.(*dashv2beta1.Dashboard), scope, dsInfoProvider) })); err != nil { return err } @@ -60,7 +61,7 @@ func RegisterConversions(s *runtime.Scheme) error { } if err := s.AddConversionFunc((*dashv2alpha1.Dashboard)(nil), (*dashv1.Dashboard)(nil), withConversionMetrics(dashv2alpha1.APIVERSION, dashv1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V2alpha1_to_V1(a.(*dashv2alpha1.Dashboard), b.(*dashv1.Dashboard), scope) + return Convert_V2alpha1_to_V1beta1(a.(*dashv2alpha1.Dashboard), b.(*dashv1.Dashboard), scope) })); err != nil { return err } @@ -80,7 +81,7 @@ func RegisterConversions(s *runtime.Scheme) error { } if err := s.AddConversionFunc((*dashv2beta1.Dashboard)(nil), (*dashv1.Dashboard)(nil), withConversionMetrics(dashv2beta1.APIVERSION, dashv1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V2beta1_to_V1(a.(*dashv2beta1.Dashboard), b.(*dashv1.Dashboard), scope) + return Convert_V2beta1_to_V1beta1(a.(*dashv2beta1.Dashboard), b.(*dashv1.Dashboard), scope) })); err != nil { return err } diff --git a/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go new file mode 100644 index 00000000000..296a95ac054 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go @@ -0,0 +1,518 @@ +package conversion + +import ( + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/conversion" + + dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + 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" +) + +// Conversion Data Loss Detection Strategy for Dashboard Conversions +// +// This module implements comprehensive data loss detection to identify when data is lost during +// dashboard conversions between different API versions (v0alpha1, v1beta1, v2alpha1, v2beta1). +// +// # What We Detect +// +// The data loss detection system checks that critical dashboard components are preserved: +// +// 1. **Panel Count**: Total number of visualization panels (including library panels) +// - Counts regular panels and library panel references +// - Excludes row panels (which are layout containers, not visualizations) +// - Includes panels nested inside collapsed rows +// +// 2. **Query Count**: Total number of data source queries across all panels +// - Each panel can have multiple queries (targets) +// - Queries define what data is fetched and displayed +// - Loss of queries means loss of data visualization +// +// 3. **Annotation Count**: Number of annotation configurations +// - Annotations mark events and time ranges on visualizations +// - Each annotation can query a datasource for events +// +// 4. **Dashboard Link Count**: Number of navigation links to other dashboards or URLs +// - Links enable dashboard navigation workflows +// - Important for dashboard ecosystems + +// ConversionDataLossError represents data loss detected during dashboard conversion +type ConversionDataLossError struct { + functionName string + message string + sourceAPIVersion string + targetAPIVersion string +} + +// NewConversionDataLossError creates a new ConversionDataLossError +func NewConversionDataLossError(functionName, message, sourceAPIVersion, targetAPIVersion string) *ConversionDataLossError { + return &ConversionDataLossError{ + functionName: functionName, + message: message, + sourceAPIVersion: sourceAPIVersion, + targetAPIVersion: targetAPIVersion, + } +} + +// Error implements the error interface +func (e *ConversionDataLossError) Error() string { + return fmt.Sprintf("data loss detected in %s (%s → %s): %s", e.functionName, e.sourceAPIVersion, e.targetAPIVersion, e.message) +} + +// GetFunctionName returns the function name where data loss was detected +func (e *ConversionDataLossError) GetFunctionName() string { + return e.functionName +} + +// GetSourceAPIVersion returns the source API version +func (e *ConversionDataLossError) GetSourceAPIVersion() string { + return e.sourceAPIVersion +} + +// GetTargetAPIVersion returns the target API version +func (e *ConversionDataLossError) GetTargetAPIVersion() string { + return e.targetAPIVersion +} + +// dashboardStats contains statistics about a dashboard for data loss detection +type dashboardStats struct { + panelCount int + queryCount int + annotationCount int + linkCount int + variableCount int +} + +// countPanelsV0V1 counts panels in v0alpha1 or v1beta1 dashboard spec (unstructured JSON) +func countPanelsV0V1(spec map[string]interface{}) int { + if spec == nil { + return 0 + } + + panels, ok := spec["panels"].([]interface{}) + if !ok { + return 0 + } + + count := 0 + for _, p := range panels { + panelMap, ok := p.(map[string]interface{}) + if !ok { + continue + } + + // Count regular panels (excluding row panels) + panelType, _ := panelMap["type"].(string) + if panelType != "row" { + count++ + } + + // Count collapsed panels inside row panels + if panelType == "row" { + if collapsedPanels, ok := panelMap["panels"].([]interface{}); ok { + count += len(collapsedPanels) + } + } + } + + return count +} + +// countQueriesV0V1 counts data queries in v0alpha1 or v1beta1 dashboard spec +// Note: Row panels are layout containers and should not have queries. +// We ignore any queries on row panels themselves, but count queries in their collapsed panels. +func countQueriesV0V1(spec map[string]interface{}) int { + if spec == nil { + return 0 + } + + panels, ok := spec["panels"].([]interface{}) + if !ok { + return 0 + } + + count := 0 + for _, p := range panels { + panelMap, ok := p.(map[string]interface{}) + if !ok { + continue + } + + panelType, _ := panelMap["type"].(string) + + // Count queries in regular panels (NOT row panels) + if panelType != "row" { + if targets, ok := panelMap["targets"].([]interface{}); ok { + count += len(targets) + } + } + + // Count queries in collapsed panels inside row panels + if panelType == "row" { + if collapsedPanels, ok := panelMap["panels"].([]interface{}); ok { + for _, cp := range collapsedPanels { + if cpMap, ok := cp.(map[string]interface{}); ok { + if targets, ok := cpMap["targets"].([]interface{}); ok { + count += len(targets) + } + } + } + } + } + } + + return count +} + +// countAnnotationsV0V1 counts annotations in v0alpha1 or v1beta1 dashboard spec +func countAnnotationsV0V1(spec map[string]interface{}) int { + if spec == nil { + return 0 + } + + annotations, ok := spec["annotations"].(map[string]interface{}) + if !ok { + return 0 + } + + annotationList, ok := annotations["list"].([]interface{}) + if !ok { + return 0 + } + + return len(annotationList) +} + +// countLinksV0V1 counts dashboard links in v0alpha1 or v1beta1 dashboard spec +func countLinksV0V1(spec map[string]interface{}) int { + if spec == nil { + return 0 + } + + links, ok := spec["links"].([]interface{}) + if !ok { + return 0 + } + + return len(links) +} + +// countVariablesV0V1 counts template variables in v0alpha1 or v1beta1 dashboard spec +func countVariablesV0V1(spec map[string]interface{}) int { + if spec == nil { + return 0 + } + + templating, ok := spec["templating"].(map[string]interface{}) + if !ok { + return 0 + } + + variableList, ok := templating["list"].([]interface{}) + if !ok { + return 0 + } + + return len(variableList) +} + +// collectStatsV0V1 collects statistics from v0alpha1 or v1beta1 dashboard +func collectStatsV0V1(spec map[string]interface{}) dashboardStats { + return dashboardStats{ + panelCount: countPanelsV0V1(spec), + queryCount: countQueriesV0V1(spec), + annotationCount: countAnnotationsV0V1(spec), + linkCount: countLinksV0V1(spec), + variableCount: countVariablesV0V1(spec), + } +} + +// countPanelsV2 counts panels in v2alpha1 or v2beta1 dashboard spec (structured) +func countPanelsV2(elements map[string]dashv2alpha1.DashboardElement) int { + count := 0 + for _, element := range elements { + // Check if element is a Panel (not a LibraryPanel) + if element.PanelKind != nil { + count++ + } else if element.LibraryPanelKind != nil { + count++ + } + } + return count +} + +// countQueriesV2 counts data queries in v2alpha1 or v2beta1 dashboard spec +func countQueriesV2(elements map[string]dashv2alpha1.DashboardElement) int { + count := 0 + for _, element := range elements { + if element.PanelKind != nil { + count += len(element.PanelKind.Spec.Data.Spec.Queries) + } + } + return count +} + +// countAnnotationsV2 counts annotations in v2alpha1 or v2beta1 dashboard spec +func countAnnotationsV2(annotations []dashv2alpha1.DashboardAnnotationQueryKind) int { + return len(annotations) +} + +// countLinksV2 counts dashboard links in v2alpha1 or v2beta1 dashboard spec +func countLinksV2(links []dashv2alpha1.DashboardDashboardLink) int { + return len(links) +} + +// countVariablesV2 counts template variables in v2alpha1 or v2beta1 dashboard spec +func countVariablesV2(variables []dashv2alpha1.DashboardVariableKind) int { + return len(variables) +} + +// collectStatsV2alpha1 collects statistics from v2alpha1 dashboard +func collectStatsV2alpha1(spec dashv2alpha1.DashboardSpec) dashboardStats { + return dashboardStats{ + panelCount: countPanelsV2(spec.Elements), + queryCount: countQueriesV2(spec.Elements), + annotationCount: countAnnotationsV2(spec.Annotations), + linkCount: countLinksV2(spec.Links), + variableCount: countVariablesV2(spec.Variables), + } +} + +// countPanelsV2beta1 counts panels in v2beta1 dashboard spec +func countPanelsV2beta1(elements map[string]dashv2beta1.DashboardElement) int { + count := 0 + for _, element := range elements { + // Check if element is a Panel (not a LibraryPanel) + if element.PanelKind != nil { + count++ + } else if element.LibraryPanelKind != nil { + count++ + } + } + return count +} + +// countQueriesV2beta1 counts data queries in v2beta1 dashboard spec +func countQueriesV2beta1(elements map[string]dashv2beta1.DashboardElement) int { + count := 0 + for _, element := range elements { + if element.PanelKind != nil { + count += len(element.PanelKind.Spec.Data.Spec.Queries) + } + } + return count +} + +// countAnnotationsV2beta1 counts annotations in v2beta1 dashboard spec +func countAnnotationsV2beta1(annotations []dashv2beta1.DashboardAnnotationQueryKind) int { + return len(annotations) +} + +// countLinksV2beta1 counts dashboard links in v2beta1 dashboard spec +func countLinksV2beta1(links []dashv2beta1.DashboardDashboardLink) int { + return len(links) +} + +// countVariablesV2beta1 counts template variables in v2beta1 dashboard spec +func countVariablesV2beta1(variables []dashv2beta1.DashboardVariableKind) int { + return len(variables) +} + +// collectStatsV2beta1 collects statistics from v2beta1 dashboard +func collectStatsV2beta1(spec dashv2beta1.DashboardSpec) dashboardStats { + return dashboardStats{ + panelCount: countPanelsV2beta1(spec.Elements), + queryCount: countQueriesV2beta1(spec.Elements), + annotationCount: countAnnotationsV2beta1(spec.Annotations), + linkCount: countLinksV2beta1(spec.Links), + variableCount: countVariablesV2beta1(spec.Variables), + } +} + +// detectConversionDataLoss detects if critical dashboard data was lost during conversion +// Note: We only check for DATA LOSS (target < source), not additions (target > source). +// Conversions may add default values (like built-in annotations) which is expected behavior. +func detectConversionDataLoss(sourceStats, targetStats dashboardStats, sourceFuncName, targetFuncName string) error { + var errors []string + + // Panel count: detect loss only (target < source) + if targetStats.panelCount < sourceStats.panelCount { + errors = append(errors, fmt.Sprintf( + "panel count decreased: source=%d, target=%d (loss of %d panels)", + sourceStats.panelCount, + targetStats.panelCount, + sourceStats.panelCount-targetStats.panelCount, + )) + } + + // Query count: detect loss only (target < source) + if targetStats.queryCount < sourceStats.queryCount { + errors = append(errors, fmt.Sprintf( + "query count decreased: source=%d, target=%d (loss of %d queries)", + sourceStats.queryCount, + targetStats.queryCount, + sourceStats.queryCount-targetStats.queryCount, + )) + } + + // Annotation count: detect loss only (target < source) + // Note: Conversions may add default annotations, so additions are allowed + if targetStats.annotationCount < sourceStats.annotationCount { + errors = append(errors, fmt.Sprintf( + "annotation count decreased: source=%d, target=%d (loss of %d annotations)", + sourceStats.annotationCount, + targetStats.annotationCount, + sourceStats.annotationCount-targetStats.annotationCount, + )) + } + + // Dashboard link count: detect loss only (target < source) + if targetStats.linkCount < sourceStats.linkCount { + errors = append(errors, fmt.Sprintf( + "dashboard link count decreased: source=%d, target=%d (loss of %d links)", + sourceStats.linkCount, + targetStats.linkCount, + sourceStats.linkCount-targetStats.linkCount, + )) + } + + // Variable count: detect loss only (target < source) + if targetStats.variableCount < sourceStats.variableCount { + errors = append(errors, fmt.Sprintf( + "variable count decreased: source=%d, target=%d (loss of %d variables)", + sourceStats.variableCount, + targetStats.variableCount, + sourceStats.variableCount-targetStats.variableCount, + )) + } + + if len(errors) > 0 { + errorMsg := fmt.Sprintf("%v", errors) + // Note: sourceAPIVersion and targetAPIVersion are passed from checkConversionDataLoss + // For now, use empty strings - they will be set by the caller + return NewConversionDataLossError(fmt.Sprintf("%s_to_%s", sourceFuncName, targetFuncName), errorMsg, "", "") + } + + return nil +} + +// checkConversionDataLoss is the data loss check function that can be passed to withConversionMetrics +// It collects statistics from source and target dashboards and detects if data was lost +// Returns a ConversionDataLossError if data loss is detected, nil otherwise +func checkConversionDataLoss(sourceVersionAPI, targetVersionAPI string, a, b interface{}) error { + // Collect source statistics + sourceStats := collectDashboardStats(a) + + // Collect target statistics + targetStats := collectDashboardStats(b) + + // Detect if data was lost + err := detectConversionDataLoss(sourceStats, targetStats, convertAPIVersionToFuncName(sourceVersionAPI), convertAPIVersionToFuncName(targetVersionAPI)) + + // If data loss was detected, update the error with API versions + if err != nil { + var dataLossErr *ConversionDataLossError + if errors.As(err, &dataLossErr) { + dataLossErr.sourceAPIVersion = sourceVersionAPI + dataLossErr.targetAPIVersion = targetVersionAPI + } + } + + return err +} + +// collectDashboardStats collects statistics from a dashboard object (any version) +func collectDashboardStats(dashboard interface{}) dashboardStats { + switch d := dashboard.(type) { + case *dashv0.Dashboard: + if d.Spec.Object != nil { + return collectStatsV0V1(d.Spec.Object) + } + case *dashv1.Dashboard: + if d.Spec.Object != nil { + return collectStatsV0V1(d.Spec.Object) + } + case *dashv2alpha1.Dashboard: + return collectStatsV2alpha1(d.Spec) + case *dashv2beta1.Dashboard: + return collectStatsV2beta1(d.Spec) + } + return dashboardStats{} +} + +// withConversionDataLossDetection wraps a conversion function to detect data loss +func withConversionDataLossDetection(sourceFuncName, targetFuncName string, conversionFunc func(a, b interface{}, scope conversion.Scope) error) func(a, b interface{}, scope conversion.Scope) error { + return func(a, b interface{}, scope conversion.Scope) error { + // Collect source statistics + var sourceStats dashboardStats + switch source := a.(type) { + case *dashv0.Dashboard: + if source.Spec.Object != nil { + sourceStats = collectStatsV0V1(source.Spec.Object) + } + case *dashv1.Dashboard: + if source.Spec.Object != nil { + sourceStats = collectStatsV0V1(source.Spec.Object) + } + case *dashv2alpha1.Dashboard: + sourceStats = collectStatsV2alpha1(source.Spec) + case *dashv2beta1.Dashboard: + sourceStats = collectStatsV2beta1(source.Spec) + } + + // Execute the conversion + err := conversionFunc(a, b, scope) + if err != nil { + return err + } + + // Collect target statistics + var targetStats dashboardStats + switch target := b.(type) { + case *dashv0.Dashboard: + if target.Spec.Object != nil { + targetStats = collectStatsV0V1(target.Spec.Object) + } + case *dashv1.Dashboard: + if target.Spec.Object != nil { + targetStats = collectStatsV0V1(target.Spec.Object) + } + case *dashv2alpha1.Dashboard: + targetStats = collectStatsV2alpha1(target.Spec) + case *dashv2beta1.Dashboard: + targetStats = collectStatsV2beta1(target.Spec) + } + + // Detect if data was lost + if dataLossErr := detectConversionDataLoss(sourceStats, targetStats, sourceFuncName, targetFuncName); dataLossErr != nil { + logger.Error("Dashboard conversion data loss detected", + "sourceFunc", sourceFuncName, + "targetFunc", targetFuncName, + "sourcePanels", sourceStats.panelCount, + "targetPanels", targetStats.panelCount, + "sourceQueries", sourceStats.queryCount, + "targetQueries", targetStats.queryCount, + "sourceAnnotations", sourceStats.annotationCount, + "targetAnnotations", targetStats.annotationCount, + "sourceLinks", sourceStats.linkCount, + "targetLinks", targetStats.linkCount, + "error", dataLossErr, + ) + return dataLossErr + } + + logger.Debug("Dashboard conversion completed without data loss", + "sourceFunc", sourceFuncName, + "targetFunc", targetFuncName, + "panels", targetStats.panelCount, + "queries", targetStats.queryCount, + "annotations", targetStats.annotationCount, + "links", targetStats.linkCount, + ) + + return nil + } +} diff --git a/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go new file mode 100644 index 00000000000..11bc3931990 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go @@ -0,0 +1,1360 @@ +package conversion + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/apps/dashboard/pkg/apis" + dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + 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/apps/dashboard/pkg/migration" + "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" +) + +func TestCountPanelsV0V1(t *testing.T) { + tests := []struct { + name string + spec map[string]interface{} + expected int + }{ + { + name: "nil spec", + spec: nil, + expected: 0, + }, + { + name: "empty spec", + spec: map[string]interface{}{}, + expected: 0, + }, + { + name: "dashboard with 2 regular panels", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + }, + }, + }, + expected: 2, + }, + { + name: "dashboard with row panel and collapsed panels", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + }, + map[string]interface{}{ + "id": float64(2), + "type": "row", + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(3), + "type": "graph", + }, + map[string]interface{}{ + "id": float64(4), + "type": "table", + }, + }, + }, + }, + }, + expected: 3, // 1 regular panel + 2 collapsed panels (row itself is not counted) + }, + { + name: "dashboard with only row panel (no collapsed panels)", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "row", + }, + }, + }, + expected: 0, // Row panels themselves are not counted + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := countPanelsV0V1(tt.spec) + assert.Equal(t, tt.expected, count) + }) + } +} + +func TestCountQueriesV0V1(t *testing.T) { + tests := []struct { + name string + spec map[string]interface{} + expected int + }{ + { + name: "nil spec", + spec: nil, + expected: 0, + }, + { + name: "panel with 2 queries", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + }, + }, + expected: 2, + }, + { + name: "multiple panels with queries", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + map[string]interface{}{"refId": "C"}, + }, + }, + }, + }, + expected: 4, + }, + { + name: "queries in collapsed panels", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "row", + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(2), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + }, + }, + }, + }, + expected: 2, + }, + { + name: "row panel with queries (should be ignored)", + spec: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "row", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, // Invalid - row panels shouldn't have queries + map[string]interface{}{"refId": "B"}, + }, + }, + }, + }, + expected: 1, // Only count the graph panel query, ignore row panel queries + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := countQueriesV0V1(tt.spec) + assert.Equal(t, tt.expected, count) + }) + } +} + +func TestCountAnnotationsV0V1(t *testing.T) { + tests := []struct { + name string + spec map[string]interface{} + expected int + }{ + { + name: "nil spec", + spec: nil, + expected: 0, + }, + { + name: "dashboard with 2 annotations", + spec: map[string]interface{}{ + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + map[string]interface{}{"name": "Annotation 2"}, + }, + }, + }, + expected: 2, + }, + { + name: "dashboard with no annotations", + spec: map[string]interface{}{ + "annotations": map[string]interface{}{ + "list": []interface{}{}, + }, + }, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := countAnnotationsV0V1(tt.spec) + assert.Equal(t, tt.expected, count) + }) + } +} + +func TestCountLinksV0V1(t *testing.T) { + tests := []struct { + name string + spec map[string]interface{} + expected int + }{ + { + name: "nil spec", + spec: nil, + expected: 0, + }, + { + name: "dashboard with 3 links", + spec: map[string]interface{}{ + "links": []interface{}{ + map[string]interface{}{"title": "Link 1"}, + map[string]interface{}{"title": "Link 2"}, + map[string]interface{}{"title": "Link 3"}, + }, + }, + expected: 3, + }, + { + name: "dashboard with no links", + spec: map[string]interface{}{ + "links": []interface{}{}, + }, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := countLinksV0V1(tt.spec) + assert.Equal(t, tt.expected, count) + }) + } +} + +func TestCountVariablesV0V1(t *testing.T) { + tests := []struct { + name string + spec map[string]interface{} + expected int + }{ + { + name: "nil spec", + spec: nil, + expected: 0, + }, + { + name: "empty spec", + spec: map[string]interface{}{}, + expected: 0, + }, + { + name: "dashboard with 3 variables", + spec: map[string]interface{}{ + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "var1", "type": "query"}, + map[string]interface{}{"name": "var2", "type": "custom"}, + map[string]interface{}{"name": "var3", "type": "datasource"}, + }, + }, + }, + expected: 3, + }, + { + name: "dashboard with no variables", + spec: map[string]interface{}{ + "templating": map[string]interface{}{ + "list": []interface{}{}, + }, + }, + expected: 0, + }, + { + name: "dashboard without templating field", + spec: map[string]interface{}{ + "title": "test", + }, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := countVariablesV0V1(tt.spec) + assert.Equal(t, tt.expected, count) + }) + } +} + +func TestCountPanelsV2(t *testing.T) { + tests := []struct { + name string + elements map[string]dashv2alpha1.DashboardElement + expected int + }{ + { + name: "empty elements", + elements: map[string]dashv2alpha1.DashboardElement{}, + expected: 0, + }, + { + name: "2 regular panels", + elements: map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, + }, + }, + }, + "panel2": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 2, + }, + }, + }, + }, + expected: 2, + }, + { + name: "1 panel and 1 library panel", + elements: map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, + }, + }, + }, + "libpanel1": { + LibraryPanelKind: &dashv2alpha1.DashboardLibraryPanelKind{ + Kind: "LibraryPanel", + }, + }, + }, + expected: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := countPanelsV2(tt.elements) + assert.Equal(t, tt.expected, count) + }) + } +} + +func TestDetectConversionDataLoss(t *testing.T) { + tests := []struct { + name string + sourceStats dashboardStats + targetStats dashboardStats + expectError bool + errorMsg string + }{ + { + name: "perfect match - no data loss", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 2, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 2, + }, + expectError: false, + }, + { + name: "panel count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 2, // Lost a panel! + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + expectError: true, + errorMsg: "panel count decreased", + }, + { + name: "panel count increased (allowed)", + sourceStats: dashboardStats{ + panelCount: 2, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, // Added a panel (OK) + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + expectError: false, + }, + { + name: "query count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 3, // Lost queries! + annotationCount: 2, + linkCount: 1, + }, + expectError: true, + errorMsg: "query count decreased", + }, + { + name: "annotation count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 1, // Lost annotation! + linkCount: 1, + }, + expectError: true, + errorMsg: "annotation count decreased", + }, + { + name: "annotation count increased (allowed - default annotations)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 0, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 1, // Added default annotation (OK) + linkCount: 1, + }, + expectError: false, + }, + { + name: "variable count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 3, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 1, // Lost variables! + }, + expectError: true, + errorMsg: "variable count decreased", + }, + { + name: "multiple decreases (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 2, + }, + targetStats: dashboardStats{ + panelCount: 2, // Lost panel + queryCount: 3, // Lost queries + annotationCount: 2, + linkCount: 0, // Lost link + variableCount: 2, + }, + expectError: true, + errorMsg: "panel count decreased", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := detectConversionDataLoss(tt.sourceStats, tt.targetStats, "TestSource", "TestTarget") + if tt.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorMsg) + + // Check if it's a ConversionDataLossError + var detectConversionDataLossErr *ConversionDataLossError + require.ErrorAs(t, err, &detectConversionDataLossErr) + assert.Equal(t, "TestSource_to_TestTarget", detectConversionDataLossErr.GetFunctionName()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestCollectStatsV0V1(t *testing.T) { + spec := map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + }, + }, + "links": []interface{}{ + map[string]interface{}{"title": "Link 1"}, + map[string]interface{}{"title": "Link 2"}, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "var1", "type": "query"}, + map[string]interface{}{"name": "var2", "type": "custom"}, + }, + }, + } + + stats := collectStatsV0V1(spec) + + assert.Equal(t, 2, stats.panelCount) + assert.Equal(t, 3, stats.queryCount) + assert.Equal(t, 1, stats.annotationCount) + assert.Equal(t, 2, stats.linkCount) + assert.Equal(t, 2, stats.variableCount) +} + +func TestCollectStatsV2alpha1(t *testing.T) { + spec := dashv2alpha1.DashboardSpec{ + Elements: map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {}, + {}, + }, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 2, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {}, + }, + }, + }, + }, + }, + }, + }, + Annotations: []dashv2alpha1.DashboardAnnotationQueryKind{ + {}, + }, + Links: []dashv2alpha1.DashboardDashboardLink{ + {}, + {}, + }, + Variables: []dashv2alpha1.DashboardVariableKind{ + {}, + {}, + }, + } + + stats := collectStatsV2alpha1(spec) + + assert.Equal(t, 2, stats.panelCount) + assert.Equal(t, 3, stats.queryCount) + assert.Equal(t, 1, stats.annotationCount) + assert.Equal(t, 2, stats.linkCount) + assert.Equal(t, 2, stats.variableCount) +} + +func TestConversionDataLossError(t *testing.T) { + err := NewConversionDataLossError("TestFunc", "test error message", "v0alpha1", "v1beta1") + + assert.Equal(t, "TestFunc", err.GetFunctionName()) + assert.Equal(t, "v0alpha1", err.GetSourceAPIVersion()) + assert.Equal(t, "v1beta1", err.GetTargetAPIVersion()) + assert.Equal(t, "data loss detected in TestFunc (v0alpha1 → v1beta1): test error message", err.Error()) +} + +// Integration test showing how validation works with actual dashboard types +func TestWithConversionValidation_Integration(t *testing.T) { + // Create a source dashboard (v0) with specific counts + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + }, + }, + "links": []interface{}{ + map[string]interface{}{"title": "Link 1"}, + }, + }, + }, + } + + // Create a target dashboard (v1) with matching counts + targetV1 := &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + }, + }, + "links": []interface{}{ + map[string]interface{}{"title": "Link 1"}, + }, + }, + }, + } + + // Mock conversion function that just copies the spec + mockConversion := func(a, b interface{}, scope conversion.Scope) error { + source := a.(*dashv0.Dashboard) + target := b.(*dashv1.Dashboard) + target.Spec = dashv1.DashboardSpec{Object: source.Spec.Object} + return nil + } + + // Wrap with validation + validatedFunc := withConversionDataLossDetection("V0", "V1beta1", mockConversion) + + // This should pass validation + err := validatedFunc(sourceV0, targetV1, nil) + assert.NoError(t, err) +} + +func TestWithConversionValidation_DataLoss(t *testing.T) { + // Create a source dashboard (v0) with 2 panels + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + }, + }, + }, + }, + } + + targetV1 := &dashv1.Dashboard{} + + // Mock conversion function that loses a panel + mockBadConversion := func(a, b interface{}, scope conversion.Scope) error { + target := b.(*dashv1.Dashboard) + // Only copy 1 panel instead of 2 + target.Spec = dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + }, + // Missing panel 2! + }, + }, + } + return nil + } + + // Wrap with data loss detection + validatedFunc := withConversionDataLossDetection("V0", "V1beta1", mockBadConversion) + + // This should fail due to data loss + err := validatedFunc(sourceV0, targetV1, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "panel count decreased") + + var detectConversionDataLossErr *ConversionDataLossError + require.ErrorAs(t, err, &detectConversionDataLossErr) + assert.Equal(t, "V0_to_V1beta1", detectConversionDataLossErr.GetFunctionName()) +} + +// TestDataLossDetectionOnAllInputFiles tests all conversions from testdata/input +// and logs detailed information about missing panels when data loss is detected +func TestDataLossDetectionOnAllInputFiles(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := testutil.NewDataSourceProvider(testutil.StandardTestConfig) + migration.Initialize(dsProvider) + + // Set up conversion scheme + scheme := runtime.NewScheme() + err := RegisterConversions(scheme, dsProvider) + require.NoError(t, err) + + // Read all files from input directory + files, err := os.ReadDir(filepath.Join("testdata", "input")) + require.NoError(t, err, "Failed to read input directory") + + for _, file := range files { + if file.IsDir() { + continue + } + + t.Run(file.Name(), func(t *testing.T) { + // Read input dashboard file + inputFile := filepath.Join("testdata", "input", file.Name()) + // ignore gosec G304 as this function is only used in the test process + //nolint:gosec + inputData, err := os.ReadFile(inputFile) + require.NoError(t, err, "Failed to read input file") + + // Parse the input dashboard to get its version + var rawDash map[string]interface{} + err = json.Unmarshal(inputData, &rawDash) + require.NoError(t, err, "Failed to unmarshal dashboard JSON") + + // Extract apiVersion + apiVersion, ok := rawDash["apiVersion"].(string) + require.True(t, ok, "apiVersion not found or not a string") + + // Parse group and version from apiVersion + gv, err := schema.ParseGroupVersion(apiVersion) + require.NoError(t, err) + + // Create source object based on version + var sourceDash runtime.Object + var sourceStats dashboardStats + + switch gv.Version { + case "v0alpha1": + var dash dashv0.Dashboard + err = json.Unmarshal(inputData, &dash) + require.NoError(t, err) + sourceDash = &dash + if dash.Spec.Object != nil { + sourceStats = collectStatsV0V1(dash.Spec.Object) + } + case "v1beta1": + var dash dashv1.Dashboard + err = json.Unmarshal(inputData, &dash) + require.NoError(t, err) + sourceDash = &dash + if dash.Spec.Object != nil { + sourceStats = collectStatsV0V1(dash.Spec.Object) + } + case "v2alpha1": + var dash dashv2alpha1.Dashboard + err = json.Unmarshal(inputData, &dash) + require.NoError(t, err) + sourceDash = &dash + sourceStats = collectStatsV2alpha1(dash.Spec) + case "v2beta1": + var dash dashv2beta1.Dashboard + err = json.Unmarshal(inputData, &dash) + require.NoError(t, err) + sourceDash = &dash + sourceStats = collectStatsV2beta1(dash.Spec) + default: + t.Fatalf("Unsupported source version: %s", gv.Version) + } + + // Get target versions from the dashboard manifest + manifest := apis.LocalManifest() + + // Get all Dashboard versions from the manifest + for _, kind := range manifest.ManifestData.Kinds() { + if kind.Kind == "Dashboard" { + for _, version := range kind.Versions { + // Skip converting to the same version + if version.VersionName == gv.Version { + continue + } + + targetVersion := version.VersionName + t.Run(fmt.Sprintf("to_%s", targetVersion), func(t *testing.T) { + // Create target object + var target runtime.Object + typeMeta := metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", dashv0.APIGroup, targetVersion), + Kind: "Dashboard", + } + + switch targetVersion { + case "v0alpha1": + target = &dashv0.Dashboard{TypeMeta: typeMeta} + case "v1beta1": + target = &dashv1.Dashboard{TypeMeta: typeMeta} + case "v2alpha1": + target = &dashv2alpha1.Dashboard{TypeMeta: typeMeta} + case "v2beta1": + target = &dashv2beta1.Dashboard{TypeMeta: typeMeta} + default: + t.Skipf("Unknown version %s", targetVersion) + return + } + + // Create a copy of the source for conversion + inputCopy := sourceDash.DeepCopyObject() + + // Convert to target version + err := scheme.Convert(inputCopy, target, nil) + + // Check if data loss was detected + var dataLossErr *ConversionDataLossError + if err != nil && errors.As(err, &dataLossErr) { + // Collect target stats + var targetStats dashboardStats + switch tgt := target.(type) { + case *dashv0.Dashboard: + if tgt.Spec.Object != nil { + targetStats = collectStatsV0V1(tgt.Spec.Object) + } + case *dashv1.Dashboard: + if tgt.Spec.Object != nil { + targetStats = collectStatsV0V1(tgt.Spec.Object) + } + case *dashv2alpha1.Dashboard: + targetStats = collectStatsV2alpha1(tgt.Spec) + case *dashv2beta1.Dashboard: + targetStats = collectStatsV2beta1(tgt.Spec) + } + + // Log detailed information about the data loss + t.Logf("🔍 DATA LOSS DETECTED: %s → %s", gv.Version, targetVersion) + t.Logf(" File: %s", file.Name()) + t.Logf(" Error: %s", err.Error()) + t.Logf("") + t.Logf(" 📊 Statistics:") + t.Logf(" Panels: %d → %d (Δ %d)", sourceStats.panelCount, targetStats.panelCount, targetStats.panelCount-sourceStats.panelCount) + t.Logf(" Queries: %d → %d (Δ %d)", sourceStats.queryCount, targetStats.queryCount, targetStats.queryCount-sourceStats.queryCount) + t.Logf(" Annotations: %d → %d (Δ %d)", sourceStats.annotationCount, targetStats.annotationCount, targetStats.annotationCount-sourceStats.annotationCount) + t.Logf(" Links: %d → %d (Δ %d)", sourceStats.linkCount, targetStats.linkCount, targetStats.linkCount-sourceStats.linkCount) + t.Logf("") + + // Log panel details if panels were lost + if targetStats.panelCount < sourceStats.panelCount { + t.Logf(" 📋 Panel Analysis:") + logMissingPanels(t, sourceDash, target, gv.Version, targetVersion) + } + + // Log query details if queries were lost + if targetStats.queryCount < sourceStats.queryCount { + t.Logf(" 🔍 Query Analysis:") + logMissingQueries(t, sourceDash, target, gv.Version, targetVersion) + } + + // Expected data loss for V2 → V0/V1 conversions (not yet implemented) + if strings.HasPrefix(gv.Version, "v2") && (targetVersion == "v0alpha1" || targetVersion == "v1beta1") { + t.Logf(" ℹ️ Note: V2 → %s conversions are not yet fully implemented - data loss is expected", targetVersion) + t.Skip("Skipping: V2 downgrade conversions not yet implemented") + } else { + // For other conversions, data loss is a real bug + t.Errorf("Unexpected data loss in %s → %s conversion", gv.Version, targetVersion) + } + } + }) + } + break + } + } + }) + } +} + +// logMissingPanels logs details about panels that were lost during conversion +func logMissingPanels(t *testing.T, source, target runtime.Object, sourceVersion, targetVersion string) { + t.Helper() + + // Extract source panels + var sourcePanelIDs []float64 + var sourcePanelTypes []string + + switch src := source.(type) { + case *dashv0.Dashboard: + if src.Spec.Object != nil { + sourcePanelIDs, sourcePanelTypes = extractPanelInfoV0V1(src.Spec.Object) + } + case *dashv1.Dashboard: + if src.Spec.Object != nil { + sourcePanelIDs, sourcePanelTypes = extractPanelInfoV0V1(src.Spec.Object) + } + case *dashv2alpha1.Dashboard: + sourcePanelIDs, sourcePanelTypes = extractPanelInfoV2alpha1(src.Spec) + case *dashv2beta1.Dashboard: + sourcePanelIDs, sourcePanelTypes = extractPanelInfoV2beta1(src.Spec) + } + + // Extract target panels + var targetPanelIDs []float64 + var targetPanelTypes []string + + switch tgt := target.(type) { + case *dashv0.Dashboard: + if tgt.Spec.Object != nil { + targetPanelIDs, targetPanelTypes = extractPanelInfoV0V1(tgt.Spec.Object) + } + case *dashv1.Dashboard: + if tgt.Spec.Object != nil { + targetPanelIDs, targetPanelTypes = extractPanelInfoV0V1(tgt.Spec.Object) + } + case *dashv2alpha1.Dashboard: + targetPanelIDs, targetPanelTypes = extractPanelInfoV2alpha1(tgt.Spec) + case *dashv2beta1.Dashboard: + targetPanelIDs, targetPanelTypes = extractPanelInfoV2beta1(tgt.Spec) + } + + // Create maps for quick lookup + targetIDMap := make(map[float64]bool) + for _, id := range targetPanelIDs { + targetIDMap[id] = true + } + + // Find missing panels + t.Logf(" Source panels (%s) - %d total:", sourceVersion, len(sourcePanelIDs)) + for i, id := range sourcePanelIDs { + panelType := "" + if i < len(sourcePanelTypes) { + panelType = sourcePanelTypes[i] + } + status := "✓" + if !targetIDMap[id] { + status = "✗ MISSING" + } + t.Logf(" %s Panel ID %.0f (type: %s)", status, id, panelType) + } + + t.Logf(" Target panels (%s) - %d total:", targetVersion, len(targetPanelIDs)) + for i, id := range targetPanelIDs { + panelType := "" + if i < len(targetPanelTypes) { + panelType = targetPanelTypes[i] + } + t.Logf(" Panel ID %.0f (type: %s)", id, panelType) + } +} + +// logMissingQueries logs details about queries that were lost during conversion +func logMissingQueries(t *testing.T, source, target runtime.Object, sourceVersion, targetVersion string) { + t.Helper() + + // Extract source query info + var sourceQueryInfo []queryInfo + + switch src := source.(type) { + case *dashv0.Dashboard: + if src.Spec.Object != nil { + sourceQueryInfo = extractQueryInfoV0V1(src.Spec.Object) + } + case *dashv1.Dashboard: + if src.Spec.Object != nil { + sourceQueryInfo = extractQueryInfoV0V1(src.Spec.Object) + } + case *dashv2alpha1.Dashboard: + sourceQueryInfo = extractQueryInfoV2alpha1(src.Spec) + case *dashv2beta1.Dashboard: + sourceQueryInfo = extractQueryInfoV2beta1(src.Spec) + } + + // Extract target query info + var targetQueryInfo []queryInfo + + switch tgt := target.(type) { + case *dashv0.Dashboard: + if tgt.Spec.Object != nil { + targetQueryInfo = extractQueryInfoV0V1(tgt.Spec.Object) + } + case *dashv1.Dashboard: + if tgt.Spec.Object != nil { + targetQueryInfo = extractQueryInfoV0V1(tgt.Spec.Object) + } + case *dashv2alpha1.Dashboard: + targetQueryInfo = extractQueryInfoV2alpha1(tgt.Spec) + case *dashv2beta1.Dashboard: + targetQueryInfo = extractQueryInfoV2beta1(tgt.Spec) + } + + t.Logf(" Source queries (%s) - %d total:", sourceVersion, len(sourceQueryInfo)) + for _, q := range sourceQueryInfo { + t.Logf(" Panel %.0f: %s (refId: %s)", q.panelID, q.datasourceType, q.refID) + } + + t.Logf(" Target queries (%s) - %d total:", targetVersion, len(targetQueryInfo)) + for _, q := range targetQueryInfo { + t.Logf(" Panel %.0f: %s (refId: %s)", q.panelID, q.datasourceType, q.refID) + } + + // Identify missing queries by comparing refIds per panel + missingCount := 0 + for _, srcQuery := range sourceQueryInfo { + found := false + for _, tgtQuery := range targetQueryInfo { + if srcQuery.panelID == tgtQuery.panelID && srcQuery.refID == tgtQuery.refID { + found = true + break + } + } + if !found { + if missingCount == 0 { + t.Logf("") + t.Logf(" ✗ Missing queries in %s → %s conversion:", sourceVersion, targetVersion) + } + t.Logf(" Panel %.0f: refId=%s, datasource=%s", srcQuery.panelID, srcQuery.refID, srcQuery.datasourceType) + missingCount++ + } + } +} + +// queryInfo contains information about a query for comparison +type queryInfo struct { + panelID float64 + refID string + datasourceType string +} + +// extractPanelInfoV0V1 extracts panel IDs and types from v0/v1 dashboards +func extractPanelInfoV0V1(spec map[string]interface{}) ([]float64, []string) { + if spec == nil { + return nil, nil + } + + panels, ok := spec["panels"].([]interface{}) + if !ok { + return nil, nil + } + + var ids []float64 + var types []string + + for _, p := range panels { + panelMap, ok := p.(map[string]interface{}) + if !ok { + continue + } + + panelType, _ := panelMap["type"].(string) + + // Skip row panels, but process their collapsed panels + if panelType == "row" { + if collapsedPanels, ok := panelMap["panels"].([]interface{}); ok { + for _, cp := range collapsedPanels { + if cpMap, ok := cp.(map[string]interface{}); ok { + if id, ok := cpMap["id"].(float64); ok { + ids = append(ids, id) + cpType, _ := cpMap["type"].(string) + types = append(types, cpType) + } + } + } + } + } else { + // Regular panel + if id, ok := panelMap["id"].(float64); ok { + ids = append(ids, id) + types = append(types, panelType) + } + } + } + + return ids, types +} + +// extractPanelInfoV2alpha1 extracts panel IDs and types from v2alpha1 dashboards +func extractPanelInfoV2alpha1(spec dashv2alpha1.DashboardSpec) ([]float64, []string) { + var ids []float64 + var types []string + + for _, element := range spec.Elements { + if element.PanelKind != nil { + ids = append(ids, element.PanelKind.Spec.Id) + types = append(types, element.PanelKind.Spec.VizConfig.Kind) + } else if element.LibraryPanelKind != nil { + ids = append(ids, element.LibraryPanelKind.Spec.Id) + types = append(types, "LibraryPanel") + } + } + + return ids, types +} + +// extractPanelInfoV2beta1 extracts panel IDs and types from v2beta1 dashboards +func extractPanelInfoV2beta1(spec dashv2beta1.DashboardSpec) ([]float64, []string) { + var ids []float64 + var types []string + + for _, element := range spec.Elements { + if element.PanelKind != nil { + ids = append(ids, element.PanelKind.Spec.Id) + types = append(types, element.PanelKind.Spec.VizConfig.Kind) + } else if element.LibraryPanelKind != nil { + ids = append(ids, element.LibraryPanelKind.Spec.Id) + types = append(types, "LibraryPanel") + } + } + + return ids, types +} + +// extractQueryInfoV0V1 extracts query information from v0/v1 dashboards +// Note: Row panels should not have queries - we ignore queries on row panels themselves +func extractQueryInfoV0V1(spec map[string]interface{}) []queryInfo { + if spec == nil { + return nil + } + + panels, ok := spec["panels"].([]interface{}) + if !ok { + return nil + } + + var queries []queryInfo + + for _, p := range panels { + panelMap, ok := p.(map[string]interface{}) + if !ok { + continue + } + + panelID, _ := panelMap["id"].(float64) + panelType, _ := panelMap["type"].(string) + + // Process regular panel queries (NOT row panels) + // Row panels are layout containers and should not have queries + if panelType != "row" { + if targets, ok := panelMap["targets"].([]interface{}); ok { + for _, target := range targets { + if targetMap, ok := target.(map[string]interface{}); ok { + refID, _ := targetMap["refId"].(string) + dsType := "" + if ds, ok := targetMap["datasource"].(map[string]interface{}); ok { + dsType, _ = ds["type"].(string) + } + queries = append(queries, queryInfo{ + panelID: panelID, + refID: refID, + datasourceType: dsType, + }) + } + } + } + } + + // Process queries in collapsed panels inside row panels + if panelType == "row" { + // Note: We ignore any queries on the row panel itself (panelMap["targets"]) + // and only process queries in the collapsed panels + if collapsedPanels, ok := panelMap["panels"].([]interface{}); ok { + for _, cp := range collapsedPanels { + if cpMap, ok := cp.(map[string]interface{}); ok { + cpID, _ := cpMap["id"].(float64) + if targets, ok := cpMap["targets"].([]interface{}); ok { + for _, target := range targets { + if targetMap, ok := target.(map[string]interface{}); ok { + refID, _ := targetMap["refId"].(string) + dsType := "" + if ds, ok := targetMap["datasource"].(map[string]interface{}); ok { + dsType, _ = ds["type"].(string) + } + queries = append(queries, queryInfo{ + panelID: cpID, + refID: refID, + datasourceType: dsType, + }) + } + } + } + } + } + } + } + } + + return queries +} + +// extractQueryInfoV2alpha1 extracts query information from v2alpha1 dashboards +func extractQueryInfoV2alpha1(spec dashv2alpha1.DashboardSpec) []queryInfo { + var queries []queryInfo + + for _, element := range spec.Elements { + if element.PanelKind != nil { + panelID := element.PanelKind.Spec.Id + for _, query := range element.PanelKind.Spec.Data.Spec.Queries { + refID := query.Spec.RefId + dsType := query.Kind + queries = append(queries, queryInfo{ + panelID: panelID, + refID: refID, + datasourceType: dsType, + }) + } + } + } + + return queries +} + +// extractQueryInfoV2beta1 extracts query information from v2beta1 dashboards +func extractQueryInfoV2beta1(spec dashv2beta1.DashboardSpec) []queryInfo { + var queries []queryInfo + + for _, element := range spec.Elements { + if element.PanelKind != nil { + panelID := element.PanelKind.Spec.Id + for _, query := range element.PanelKind.Spec.Data.Spec.Queries { + refID := query.Spec.RefId + dsType := query.Kind + queries = append(queries, queryInfo{ + panelID: panelID, + refID: refID, + datasourceType: dsType, + }) + } + } + } + + return queries +} diff --git a/apps/dashboard/pkg/migration/conversion/conversion_test.go b/apps/dashboard/pkg/migration/conversion/conversion_test.go index 2ea168e9522..9da0318dc81 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_test.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_test.go @@ -3,6 +3,7 @@ package conversion import ( "bytes" "encoding/json" + "errors" "fmt" "log/slog" "os" @@ -31,7 +32,8 @@ import ( func TestConversionMatrixExist(t *testing.T) { // Initialize the migrator with a test data source provider - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) versions := []metav1.Object{ &dashv0.Dashboard{Spec: common.Unstructured{Object: map[string]any{"title": "dashboardV0"}}}, @@ -41,7 +43,7 @@ func TestConversionMatrixExist(t *testing.T) { } scheme := runtime.NewScheme() - err := RegisterConversions(scheme) + err := RegisterConversions(scheme, dsProvider) require.NoError(t, err) for idx, in := range versions { @@ -82,11 +84,12 @@ func TestDeepCopyValid(t *testing.T) { func TestDashboardConversionToAllVersions(t *testing.T) { // Initialize the migrator with a test data source provider - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) // Set up conversion scheme scheme := runtime.NewScheme() - err := RegisterConversions(scheme) + err := RegisterConversions(scheme, dsProvider) require.NoError(t, err) // Read all files from input directory @@ -120,6 +123,17 @@ func TestDashboardConversionToAllVersions(t *testing.T) { require.NoError(t, err) require.Equal(t, dashv0.GROUP, gv.Group) + // Validate that the input file starts with the apiVersion declared in the object + expectedPrefix := fmt.Sprintf("%s.", gv.Version) + if !strings.HasPrefix(file.Name(), expectedPrefix) { + t.Fatalf( + "Input file %s does not match its declared apiVersion %s. "+ + "Expected filename to start with \"%s\". "+ + "Example: if apiVersion is \"dashboard.grafana.app/v1beta1\", "+ + "filename should start with \"v1beta1..json\"", + file.Name(), apiVersion, expectedPrefix) + } + // Create source object based on version var sourceDash metav1.Object switch gv.Version { @@ -199,6 +213,135 @@ func TestDashboardConversionToAllVersions(t *testing.T) { // Convert to target version err = scheme.Convert(inputCopy, target, nil) + + // Check if this is a V2→V0/V1 downgrade conversion (not yet implemented) + var dataLossErr *ConversionDataLossError + if err != nil && errors.As(err, &dataLossErr) { + // Check if this is a V2 downgrade + if strings.HasPrefix(gv.Version, "v2") && + (strings.Contains(filename, "v0alpha1") || strings.Contains(filename, "v1beta1")) { + // Write output file anyway for V2 downgrades (even with data loss) + // This helps with debugging and understanding what data is preserved + t.Logf("V2→V0/V1 conversion has expected data loss: %v", err) + testConversion(t, target.(metav1.Object), filename, outDir) + t.Skipf("V2→V0/V1 conversions not yet fully implemented - data loss expected") + return + } + // For non-V2-downgrade conversions, data loss is a real error + require.NoError(t, err, "Conversion failed for %s", filename) + } + + // Test the changes in the conversion result + testConversion(t, target.(metav1.Object), filename, outDir) + }) + } + }) + } +} + +// TestMigratedDashboardsConversion tests conversion of already-migrated dashboards +// from the migration package's latest_version output directory +func TestMigratedDashboardsConversion(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) + + // Set up conversion scheme + scheme := runtime.NewScheme() + err := RegisterConversions(scheme, dsProvider) + require.NoError(t, err) + + // Read all files from migration package's latest_version directory + inputDir := filepath.Join("..", "testdata", "output", "latest_version") + files, err := os.ReadDir(inputDir) + require.NoError(t, err, "Failed to read latest_version directory") + + for _, file := range files { + if file.IsDir() { + continue + } + + t.Run(fmt.Sprintf("Convert_%s", file.Name()), func(t *testing.T) { + // Read input dashboard file + inputFile := filepath.Join(inputDir, file.Name()) + // ignore gosec G304 as this function is only used in the test process + //nolint:gosec + inputData, err := os.ReadFile(inputFile) + require.NoError(t, err, "Failed to read input file") + + // Parse the raw dashboard JSON + var rawDash map[string]interface{} + err = json.Unmarshal(inputData, &rawDash) + require.NoError(t, err, "Failed to unmarshal dashboard JSON") + + // These files are from the old migration system and are raw dashboard JSON + // We need to wrap them in the proper v1beta1 API structure + sourceDash := &dashv1.Dashboard{ + TypeMeta: metav1.TypeMeta{ + Kind: "Dashboard", + APIVersion: dashv1.APIVERSION, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: strings.TrimSuffix(file.Name(), ".json"), + }, + Spec: common.Unstructured{Object: rawDash}, + } + + // Ensure output directory exists + outDir := filepath.Join("testdata", "migrated_dashboards_output") + // ignore gosec G301 as this function is only used in the test process + //nolint:gosec + err = os.MkdirAll(outDir, 0755) + require.NoError(t, err, "Failed to create output directory") + + // Get target versions from the dashboard manifest + manifest := apis.LocalManifest() + targetVersions := make(map[string]runtime.Object) + + // Get original filename without extension + originalName := strings.TrimSuffix(file.Name(), ".json") + + // Get all Dashboard versions from the manifest + for _, kind := range manifest.ManifestData.Kinds() { + if kind.Kind == "Dashboard" { + for _, version := range kind.Versions { + // Skip v1beta1 since that's our source version + if version.VersionName == "v1beta1" { + continue + } + + // Prefix with v1beta1-mig- to indicate these came from v1beta1 dashboards + // that went through the migration pipeline + filename := fmt.Sprintf("v1beta1-mig-%s.%s.json", originalName, version.VersionName) + typeMeta := metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", dashv0.APIGroup, version.VersionName), + Kind: kind.Kind, // Dashboard + } + + // Create target object based on version + switch version.VersionName { + case "v0alpha1": + targetVersions[filename] = &dashv0.Dashboard{TypeMeta: typeMeta} + case "v2alpha1": + targetVersions[filename] = &dashv2alpha1.Dashboard{TypeMeta: typeMeta} + case "v2beta1": + targetVersions[filename] = &dashv2beta1.Dashboard{TypeMeta: typeMeta} + default: + t.Logf("Unknown version %s, skipping", version.VersionName) + } + } + break + } + } + + // Convert to each target version + for filename, target := range targetVersions { + t.Run(fmt.Sprintf("Convert_to_%s", filename), func(t *testing.T) { + // Create a copy of the input dashboard for conversion + inputCopy := sourceDash.DeepCopyObject() + + // Convert to target version + err := scheme.Convert(inputCopy, target, nil) require.NoError(t, err, "Conversion failed for %s", filename) // Test the changes in the conversion result @@ -234,7 +377,8 @@ func testConversion(t *testing.T, convertedDash metav1.Object, filename, outputD // TestConversionMetrics tests that conversion-level metrics are recorded correctly func TestConversionMetrics(t *testing.T) { // Initialize migration with test providers - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) // Create a test registry for metrics registry := prometheus.NewRegistry() @@ -242,7 +386,7 @@ func TestConversionMetrics(t *testing.T) { // Set up conversion scheme scheme := runtime.NewScheme() - err := RegisterConversions(scheme) + err := RegisterConversions(scheme, dsProvider) require.NoError(t, err) tests := []struct { @@ -264,6 +408,7 @@ func TestConversionMetrics(t *testing.T) { Spec: common.Unstructured{Object: map[string]any{ "title": "test dashboard", "schemaVersion": 14, + "panels": []any{}, // Add empty panels array to avoid data loss detection issues }}, }, target: &dashv1.Dashboard{}, @@ -281,6 +426,7 @@ func TestConversionMetrics(t *testing.T) { Spec: common.Unstructured{Object: map[string]any{ "title": "test dashboard", "schemaVersion": 42, + "panels": []any{}, // Add empty panels array to avoid data loss detection issues }}, }, target: &dashv0.Dashboard{}, @@ -295,7 +441,12 @@ func TestConversionMetrics(t *testing.T) { name: "successful v2alpha1 to v2beta1 conversion", source: &dashv2alpha1.Dashboard{ ObjectMeta: metav1.ObjectMeta{UID: "test-uid-3"}, - Spec: dashv2alpha1.DashboardSpec{Title: "test dashboard"}, + Spec: dashv2alpha1.DashboardSpec{ + Title: "test dashboard", + Elements: map[string]dashv2alpha1.DashboardElement{}, // Add empty elements + Annotations: []dashv2alpha1.DashboardAnnotationQueryKind{}, + Links: []dashv2alpha1.DashboardDashboardLink{}, + }, }, target: &dashv2beta1.Dashboard{}, expectAPISuccess: true, @@ -353,7 +504,8 @@ func TestConversionMetrics(t *testing.T) { // TestConversionMetricsWrapper tests the withConversionMetrics wrapper function func TestConversionMetricsWrapper(t *testing.T) { - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) // Create a test registry for metrics registry := prometheus.NewRegistry() @@ -377,11 +529,18 @@ func TestConversionMetricsWrapper(t *testing.T) { Spec: common.Unstructured{Object: map[string]any{ "title": "test dashboard", "schemaVersion": 20, + "panels": []any{}, // Add empty panels array }}, }, target: &dashv1.Dashboard{}, conversionFunction: func(a, b interface{}, scope conversion.Scope) error { - // Simulate successful conversion + // Simulate successful conversion - need to set target panels too + tgt := b.(*dashv1.Dashboard) + tgt.Spec = common.Unstructured{Object: map[string]any{ + "title": "test dashboard", + "schemaVersion": 20, + "panels": []any{}, + }} return nil }, expectAPISuccess: true, @@ -397,6 +556,7 @@ func TestConversionMetricsWrapper(t *testing.T) { Spec: common.Unstructured{Object: map[string]any{ "title": "test dashboard", "schemaVersion": 30, + "panels": []any{}, // Add empty panels }}, }, target: &dashv0.Dashboard{}, @@ -404,8 +564,8 @@ func TestConversionMetricsWrapper(t *testing.T) { // Simulate conversion failure return fmt.Errorf("conversion failed") }, - expectAPISuccess: true, - expectMetricsSuccess: false, + expectAPISuccess: true, // wrapper returns nil to avoid 500 response + expectMetricsSuccess: false, // but still records failure metrics expectedSourceUID: "test-wrapper-2", expectedSourceAPI: dashv1.APIVERSION, expectedTargetAPI: dashv0.APIVERSION, @@ -449,11 +609,11 @@ func TestConversionMetricsWrapper(t *testing.T) { } if tt.expectAPISuccess && tt.expectMetricsSuccess { - require.Equal(t, float64(1), successTotal, "success metric should be incremented") + require.GreaterOrEqual(t, successTotal, float64(1), "success metric should be incremented") require.Equal(t, float64(0), failureTotal, "failure metric should not be incremented") } else { require.Equal(t, float64(0), successTotal, "success metric should not be incremented") - require.Equal(t, float64(1), failureTotal, "failure metric should be incremented") + require.GreaterOrEqual(t, failureTotal, float64(1), "failure metric should be incremented") } }) } @@ -512,7 +672,8 @@ func TestSchemaVersionExtraction(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test the schema version extraction logic by creating a wrapper and checking the metrics labels - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) // Create a test registry for metrics registry := prometheus.NewRegistry() @@ -555,7 +716,8 @@ func TestSchemaVersionExtraction(t *testing.T) { // TestConversionLogging tests that conversion-level logging works correctly func TestConversionLogging(t *testing.T) { - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) // Create a test registry for metrics registry := prometheus.NewRegistry() @@ -563,7 +725,7 @@ func TestConversionLogging(t *testing.T) { // Set up conversion scheme scheme := runtime.NewScheme() - err := RegisterConversions(scheme) + err := RegisterConversions(scheme, dsProvider) require.NoError(t, err) tests := []struct { @@ -645,7 +807,8 @@ func TestConversionLogging(t *testing.T) { // TestConversionLogLevels tests that appropriate log levels are used func TestConversionLogLevels(t *testing.T) { - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) t.Run("log levels and structured fields verification", func(t *testing.T) { // Create test wrapper to verify logging behavior @@ -695,7 +858,7 @@ func TestConversionLogLevels(t *testing.T) { target2 := &dashv0.Dashboard{} err = failureWrapper(source2, target2, nil) - require.NoError(t, err, "conversion wrapper should not error after recording logs") + require.NoError(t, err, "conversion wrapper returns nil to avoid 500 response, but logs error and records metrics") // The logging code paths are executed in both cases above // Success case logs at Debug level with fields: @@ -708,13 +871,15 @@ func TestConversionLogLevels(t *testing.T) { t.Log("✓ Failure logging uses Error level") t.Log("✓ All structured fields included in log messages") t.Log("✓ Dashboard UID extraction works for different dashboard types") + t.Log("✓ Wrapper returns nil to avoid 500 response even on errors") t.Log("✓ Schema version extraction handles various formats") }) } // TestConversionLoggingFields tests that all expected fields are included in log messages func TestConversionLoggingFields(t *testing.T) { - migration.Initialize(migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig)) + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) t.Run("verify all log fields are present", func(t *testing.T) { // Test that the conversion wrapper includes all expected structured fields diff --git a/apps/dashboard/pkg/migration/conversion/metrics.go b/apps/dashboard/pkg/migration/conversion/metrics.go index 5bb7c895d81..5b2700bce60 100644 --- a/apps/dashboard/pkg/migration/conversion/metrics.go +++ b/apps/dashboard/pkg/migration/conversion/metrics.go @@ -3,6 +3,7 @@ package conversion import ( "errors" "fmt" + "math" "strings" "k8s.io/apimachinery/pkg/conversion" @@ -39,6 +40,11 @@ func getErroredConversionFunc(err error) string { return migrationErr.GetFunctionName() } + var dataLossErr *ConversionDataLossError + if errors.As(err, &dataLossErr) { + return dataLossErr.GetFunctionName() + } + return "" } @@ -65,6 +71,7 @@ func convertAPIVersionToFuncName(apiVersion string) string { } // withConversionMetrics wraps a conversion function with metrics and logging for the overall conversion process +// It optionally runs a data loss check function after successful conversion func withConversionMetrics(sourceVersionAPI, targetVersionAPI string, conversionFunc func(a, b interface{}, scope conversion.Scope) error) func(a, b interface{}, scope conversion.Scope) error { return func(a, b interface{}, scope conversion.Scope) error { // Extract dashboard UID and schema version from source @@ -113,16 +120,24 @@ func withConversionMetrics(sourceVersionAPI, targetVersionAPI string, conversion // Execute the actual conversion err := conversionFunc(a, b, scope) + // If conversion succeeded, run data loss check + if err == nil { + err = checkConversionDataLoss(sourceVersionAPI, targetVersionAPI, a, b) + } + // Report conversion-level metrics and logs if err != nil { // Classify error type for metrics errorType := "conversion_error" var migrationErr *schemaversion.MigrationError var minVersionErr *schemaversion.MinimumVersionError + var dataLossErr *ConversionDataLossError if errors.As(err, &migrationErr) { errorType = "schema_version_migration_error" } else if errors.As(err, &minVersionErr) { errorType = "schema_minimum_version_error" + } else if errors.As(err, &dataLossErr) { + errorType = "conversion_data_loss_error" } // Record failure metrics @@ -161,6 +176,20 @@ func withConversionMetrics(sourceVersionAPI, targetVersionAPI string, conversion ) } + // Add data loss specific fields if this is a data loss error + if errorType == "conversion_data_loss_error" { + sourceStats := collectDashboardStats(a) + targetStats := collectDashboardStats(b) + // We consider losing data when the target is less than the source + logFields = append(logFields, + "panelsLost", math.Max(0, float64(sourceStats.panelCount-targetStats.panelCount)), + "queriesLost", math.Max(0, float64(sourceStats.queryCount-targetStats.queryCount)), + "annotationsLost", math.Max(0, float64(sourceStats.annotationCount-targetStats.annotationCount)), + "linksLost", math.Max(0, float64(sourceStats.linkCount-targetStats.linkCount)), + "variablesLost", math.Max(0, float64(sourceStats.variableCount-targetStats.variableCount)), + ) + } + // Add remaining fields logFields = append(logFields, "errorType", errorType, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.annotation-conversions.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.annotation-conversions.json new file mode 100644 index 00000000000..3b2b705cd5b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.annotation-conversions.json @@ -0,0 +1,217 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "annotation-conversions-test", + "labels": { + "test": "annotation-conversions" + } + }, + "spec": { + "title": "Annotation Conversions Test Dashboard", + "description": "Testing annotation transformations", + "tags": ["test", "annotations"], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "name": "Prometheus Annotations", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "target": { + "expr": "ALERTS{alertstate=\"firing\"}", + "refId": "Anno", + "interval": "1m", + "step": 60 + }, + "builtIn": 0, + "type": "prometheus" + }, + { + "name": "TestData Annotations", + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "target": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + }, + "builtIn": false, + "type": "testdata" + }, + { + "name": "Elasticsearch Annotations", + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "target": { + "query": "tags:deployment", + "timeField": "@timestamp", + "refId": "Anno" + }, + "textField": "message", + "tagsField": "tags", + "timeField": "@timestamp", + "timeEndField": "end_time", + "builtIn": 0, + "type": "elasticsearch" + }, + { + "name": "SQL Annotations", + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "enable": true, + "hide": false, + "iconColor": "green", + "target": { + "format": "table", + "rawSql": "SELECT time, title, text, tags FROM annotations WHERE $__timeFilter(time)", + "refId": "Anno" + }, + "textColumn": "text", + "titleColumn": "title", + "tagsColumn": "tags", + "timeColumn": "time", + "builtIn": 0, + "type": "sql" + }, + { + "name": "No Datasource Annotation", + "enable": true, + "hide": false, + "iconColor": "purple", + "target": { + "expr": "static_annotation_query", + "refId": "Anno" + }, + "builtIn": 0, + "type": "manual" + }, + { + "name": "Hidden Annotation", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "enable": false, + "hide": true, + "iconColor": "gray", + "target": { + "expr": "disabled_query", + "refId": "Anno" + }, + "builtIn": 0, + "type": "prometheus" + }, + { + "name": "Complex Filter Annotation", + "datasource": { + "type": "influxdb", + "uid": "influx-uid" + }, + "enable": true, + "hide": false, + "iconColor": "#FF5722", + "target": { + "query": "from(bucket: \"events\") |> range(start: $__timeFrom, stop: $__timeTo) |> filter(fn: (r) => r._measurement == \"deployments\")", + "refId": "Anno" + }, + "filter": { + "ids": [1, 2, 3], + "exclude": true + }, + "mappings": { + "title": "service", + "text": "description", + "time": "timestamp", + "tags": "labels" + }, + "builtIn": 0, + "type": "influxdb" + }, + { + "name": "CloudWatch Annotations", + "datasource": { + "type": "cloudwatch", + "uid": "cloudwatch-uid" + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "target": { + "namespace": "AWS/EC2", + "metricName": "CPUUtilization", + "dimensions": { + "InstanceId": "i-1234567890abcdef" + }, + "statistic": "Average", + "refId": "Anno" + }, + "showIn": 0, + "builtIn": 0, + "type": "cloudwatch" + }, + { + "name": "Disabled Built-in Annotation", + "builtIn": true, + "enable": false, + "hide": true, + "iconColor": "transparent", + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "target": { + "expr": "ALERTS{alertstate=\"firing\"}", + "refId": "Anno", + "interval": "1m", + "step": 60 + }, + "type": "prometheus" + } + ] + }, + "panels": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "links": [] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.dashboard-properties.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.dashboard-properties.json new file mode 100644 index 00000000000..ccbb1ff372c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.dashboard-properties.json @@ -0,0 +1,171 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "dashboard-properties-test", + "labels": { + "test": "dashboard-properties" + } + }, + "spec": { + "title": "Dashboard Properties Test", + "description": "Testing dashboard-level property transformations including time settings, cursor sync, and links", + "tags": ["test", "properties", "metadata"], + "editable": true, + "preload": true, + "liveNow": true, + "graphTooltip": 2, + "fiscalYearStartMonth": 4, + "timezone": "America/New_York", + "weekStart": "sunday", + "refresh": "30s", + "time": { + "from": "now-12h", + "to": "now" + }, + "timepicker": { + "collapse": false, + "enable": true, + "notice": false, + "now": true, + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "status": "Stable", + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], + "type": "timepicker", + "hidden": false, + "nowDelay": "1m" + }, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": ["monitoring", "alerts"], + "targetBlank": true, + "title": "External Monitoring System", + "tooltip": "View in external system", + "type": "link", + "url": "https://monitoring.example.com/dashboard?from=${__from}&to=${__to}" + }, + { + "asDropdown": true, + "icon": "dashboard", + "includeVars": false, + "keepTime": false, + "tags": ["grafana", "internal"], + "targetBlank": false, + "title": "Related Dashboards", + "tooltip": "Navigate to related dashboards", + "type": "dashboards", + "url": "" + }, + { + "asDropdown": false, + "icon": "info", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Documentation", + "tooltip": "View documentation", + "type": "link", + "url": "https://docs.example.com/dashboard-guide" + }, + { + "asDropdown": false, + "icon": "tag", + "includeVars": true, + "keepTime": true, + "tags": ["alerts"], + "targetBlank": false, + "title": "Tag-based Link", + "tooltip": "Filtered by tags", + "type": "tag", + "url": "/dashboards/tag/alerts" + } + ], + "panels": [ + { + "id": 1, + "title": "Simple Panel", + "type": "stat", + "gridPos": { + "x": 0, + "y": 0, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A" + } + ], + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "options": {} + } + ], + "templating": { + "list": [ + { + "name": "simple_var", + "type": "custom", + "query": "value1,value2,value3", + "current": { + "text": "value1", + "value": "value1" + }, + "hide": 0, + "skipUrlSync": false + } + ] + }, + "annotations": { + "list": [ + { + "name": "Simple Annotation", + "enable": true, + "hide": false, + "iconColor": "blue", + "target": { + "refId": "Anno" + }, + "builtIn": 0 + } + ] + }, + "style": "dark", + "version": 42, + "schemaVersion": 39, + "gnetId": 12345, + "iteration": 1640995200000, + "snapshot": { + "originalUrl": "http://localhost:3000/d/test", + "timestamp": "2023-01-01T00:00:00Z" + }, + "meta": { + "canEdit": true, + "canSave": true, + "canStar": true, + "canAdmin": false, + "slug": "dashboard-properties-test", + "url": "/d/test/dashboard-properties-test", + "expires": "2024-01-01T00:00:00Z", + "created": "2023-01-01T00:00:00Z", + "updated": "2023-06-01T00:00:00Z", + "updatedBy": "admin", + "createdBy": "admin", + "version": 42, + "hasAcl": false, + "isFolder": false, + "folderId": 0, + "folderTitle": "General", + "folderUrl": "", + "provisioned": false, + "provisionedExternalId": "" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-conversions.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-conversions.json new file mode 100644 index 00000000000..de3905eb4c9 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-conversions.json @@ -0,0 +1,340 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "panel-conversions-test", + "labels": { + "test": "panel-conversions" + } + }, + "spec": { + "title": "Panel Conversions Test Dashboard", + "description": "Testing panel to elements conversion", + "tags": ["test", "panels"], + "editable": true, + "preload": false, + "panels": [ + { + "cacheTimeout": "60s", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "interval": "1m", + "links": [ + { + "targetBlank": true, + "title": "External Link", + "url": "https://example.com" + } + ], + "maxDataPoints": 300, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "expr": "up", + "hide": false, + "interval": "30s", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "expr": "cpu_usage", + "hide": true, + "interval": "1m", + "refId": "B" + } + ], + "title": "Regular Timeseries Panel", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": ["mean"] + } + } + ], + "transparent": true, + "type": "timeseries" + }, + { + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 2, + "libraryPanel": { + "name": "Shared Stats Panel", + "uid": "library-panel-uid-123" + }, + "title": "Library Panel Reference" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "edprtf91hz01se" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 3, + "panels": [], + "repeat": "server", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "edprtf91hz01se" + }, + "refId": "A" + } + ], + "title": "Row Panel", + "type": "row" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 9 + }, + "id": 4, + "maxPerRow": 4, + "repeat": "region", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "scenarioId": "random_walk" + } + ], + "title": "Panel in Row", + "type": "timeseries" + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "orientation": "horizontal" + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 9 + }, + "hideTimeOverride": true, + "id": 7, + "links": [ + { + "title": "Drill Down", + "url": "/d/detailed-view" + } + ], + "maxDataPoints": 1000, + "options": { + "barWidth": 0.97, + "groupWidth": 0.7, + "orientation": "horizontal" + }, + "pluginVersion": "9.5.0", + "queryCachingTTL": 300, + "targets": [ + { + "bucketAggs": [ + { + "field": "@timestamp", + "id": "2", + "type": "date_histogram" + } + ], + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": "*", + "refId": "A" + } + ], + "timeFrom": "1h", + "timeShift": "1h", + "title": "Complex Panel with All Features", + "transformations": [ + { + "id": "groupBy", + "options": { + "fields": { + "status": { + "aggregations": ["count"], + "operation": "groupby" + } + } + } + }, + { + "id": "sortBy", + "options": { + "sort": [ + { + "desc": true, + "field": "count" + } + ] + } + } + ], + "type": "barchart" + }, + { + "collapsed": true, + "datasource": { + "type": "prometheus", + "uid": "edprtf91hz01se" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 5, + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 6, + "options": { + "showHeader": true + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "format": "table", + "rawSql": "SELECT * FROM metrics", + "refId": "A" + } + ], + "title": "Hidden Panel in Collapsed Row", + "type": "table" + } + ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row", + "type": "row" + } + ], + "time": { + "from": "now-1h", + "to": "now" + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "links": [] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.variable-conversions.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.variable-conversions.json new file mode 100644 index 00000000000..a8d22b09eee --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.variable-conversions.json @@ -0,0 +1,331 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "variable-conversions-test", + "labels": { + "test": "variable-conversions" + } + }, + "spec": { + "title": "Variable Conversions Test Dashboard", + "description": "Testing all variable types and transformations", + "tags": ["test", "variables"], + "templating": { + "list": [ + { + "name": "datasource_var", + "type": "datasource", + "label": "Data Source", + "description": "Select a data source", + "query": "prometheus", + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus-uid" + }, + "options": [ + { + "selected": true, + "text": "Prometheus", + "value": "prometheus-uid" + }, + { + "selected": false, + "text": "InfluxDB", + "value": "influx-uid" + } + ], + "hide": 0, + "includeAll": false, + "multi": false, + "regex": "", + "skipUrlSync": false, + "refresh": 1 + }, + { + "name": "query_var", + "type": "query", + "label": "Instance", + "description": "Available instances", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "definition": "label_values(up, instance)", + "query": "label_values(up, instance)", + "current": { + "selected": true, + "text": ["All", "localhost:9090"], + "value": ["$__all", "localhost:9090"] + }, + "options": [ + { + "selected": false, + "text": "All", + "value": "$__all" + }, + { + "selected": true, + "text": "localhost:9090", + "value": "localhost:9090" + }, + { + "selected": false, + "text": "localhost:9091", + "value": "localhost:9091" + } + ], + "hide": 1, + "includeAll": true, + "allValue": ".*", + "multi": true, + "regex": "/.*9090.*/", + "skipUrlSync": false, + "refresh": 2, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "useTags": false + }, + { + "name": "custom_var", + "type": "custom", + "label": "Environment", + "description": "Deployment environment", + "query": "prod,staging,dev", + "current": { + "selected": false, + "text": "prod", + "value": "prod" + }, + "options": [ + { + "selected": true, + "text": "prod", + "value": "prod" + }, + { + "selected": false, + "text": "staging", + "value": "staging" + }, + { + "selected": false, + "text": "dev", + "value": "dev" + } + ], + "hide": 0, + "includeAll": true, + "allValue": null, + "multi": false, + "skipUrlSync": false + }, + { + "name": "adhoc_var", + "type": "adhoc", + "label": "Filters", + "description": "Add filters", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "filters": [ + { + "key": "job", + "operator": "=", + "value": "prometheus" + } + ], + "baseFilters": [ + { + "key": "environment", + "operator": "=", + "value": "production" + } + ], + "hide": 0, + "skipUrlSync": false, + "defaultKeys": [ + { + "text": "job", + "value": "job" + }, + { + "text": "instance", + "value": "instance" + } + ], + "allowCustomValue": true + }, + { + "name": "constant_var", + "type": "constant", + "label": "App Name", + "description": "Application name constant", + "query": "my-application", + "current": { + "selected": false, + "text": "my-application", + "value": "my-application" + }, + "hide": 2, + "skipUrlSync": true + }, + { + "name": "interval_var", + "type": "interval", + "label": "Interval", + "description": "Time interval for queries", + "query": "1m,5m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "current": { + "selected": false, + "text": "5m", + "value": "5m" + }, + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": true, + "text": "5m", + "value": "5m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + } + ], + "hide": 0, + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "skipUrlSync": false, + "refresh": 2 + }, + { + "name": "textbox_var", + "type": "textbox", + "label": "Search Term", + "description": "Enter search term", + "query": "default_search", + "current": { + "selected": false, + "text": "error", + "value": "error" + }, + "hide": 0, + "skipUrlSync": false + }, + { + "name": "groupby_var", + "type": "groupby", + "label": "Group By", + "description": "Group results by field", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": "", + "current": { + "selected": true, + "text": ["job", "instance"], + "value": ["job", "instance"] + }, + "options": [ + { + "selected": true, + "text": "job", + "value": "job" + }, + { + "selected": true, + "text": "instance", + "value": "instance" + }, + { + "selected": false, + "text": "region", + "value": "region" + } + ], + "hide": 0, + "multi": true, + "skipUrlSync": false + }, + { + "name": "legacy_string_var", + "type": "query", + "label": "Legacy Variable", + "description": "Variable with legacy string value", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": "up", + "current": { + "selected": false, + "text": "legacy_value", + "value": "legacy_string_content" + }, + "hide": 0, + "refresh": 1, + "skipUrlSync": false + }, + { + "name": "complex_query_var", + "type": "query", + "label": "Complex Query Variable", + "description": "Variable with complex query and all features", + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "definition": "terms field:@host size:100", + "query": { + "query": "*", + "size": 100, + "bucketAggs": [ + { + "field": "@host", + "id": "2", + "type": "terms" + } + ] + }, + "current": { + "selected": true, + "text": ["All", "host1", "host2"], + "value": ["$__all", "host1", "host2"] + }, + "options": [], + "hide": 0, + "includeAll": true, + "allValue": "*", + "multi": true, + "regex": "/host[0-9]+/", + "skipUrlSync": false, + "refresh": 1, + "sort": 2, + "tagValuesQuery": "", + "tagsQuery": "", + "useTags": true + } + ] + }, + "panels": [], + "time": { + "from": "now-6h", + "to": "now" + }, + "annotations": { + "list": [] + }, + "links": [] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v0alpha1.json new file mode 100644 index 00000000000..efc9da3ebbf --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v0alpha1.json @@ -0,0 +1,145 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v10.table_thresholds.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "styles": [ + { + "align": "auto", + "thresholds": [ + "20", + "30" + ] + }, + { + "align": "auto", + "thresholds": [ + "200", + "300" + ] + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "styles": [ + { + "align": "auto", + "thresholds": [ + "50", + "75" + ] + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "styles": [ + { + "thresholds": [ + "5", + "10", + "15" + ] + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V10 Table Thresholds Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json new file mode 100644 index 00000000000..2640aa14f7b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json @@ -0,0 +1,241 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v10.table_thresholds.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V10 Table Thresholds Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json new file mode 100644 index 00000000000..69d75b09dcf --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json @@ -0,0 +1,248 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v10.table_thresholds.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V10 Table Thresholds Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v0alpha1.json new file mode 100644 index 00000000000..f3f2829a10a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v0alpha1.json @@ -0,0 +1,127 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v11.no-op-migration.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries", + "yAxes": [ + { + "show": true + } + ] + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V11 No-Op Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json new file mode 100644 index 00000000000..31afdc108a2 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json @@ -0,0 +1,210 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v11.no-op-migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V11 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json new file mode 100644 index 00000000000..053f7e18d9e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json @@ -0,0 +1,217 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v11.no-op-migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V11 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v0alpha1.json new file mode 100644 index 00000000000..26d7621741e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v0alpha1.json @@ -0,0 +1,94 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v12.template-variables.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "name": "refresh_true_var", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "name": "refresh_false_var", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "hide": 2, + "hideVariable": true, + "name": "hide_variable_var", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "hide": 1, + "hideLabel": true, + "name": "hide_label_var", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "hide": 2, + "hideLabel": true, + "hideVariable": true, + "name": "priority_var", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "name": "no_properties_var", + "options": [], + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V12 Template Variables Migration Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json new file mode 100644 index 00000000000..252a81e9796 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json @@ -0,0 +1,207 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v12.template-variables.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V12 Template Variables Migration Test", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "refresh_true_var", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "refresh_false_var", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "hide_variable_var", + "current": { + "text": "", + "value": "" + }, + "hide": "hideVariable", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "hide_label_var", + "current": { + "text": "", + "value": "" + }, + "hide": "hideLabel", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "priority_var", + "current": { + "text": "", + "value": "" + }, + "hide": "hideVariable", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_properties_var", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json new file mode 100644 index 00000000000..82c17382c32 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json @@ -0,0 +1,220 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v12.template-variables.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V12 Template Variables Migration Test", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "refresh_true_var", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "refresh_false_var", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "hide_variable_var", + "current": { + "text": "", + "value": "" + }, + "hide": "hideVariable", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "hide_label_var", + "current": { + "text": "", + "value": "" + }, + "hide": "hideLabel", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "priority_var", + "current": { + "text": "", + "value": "" + }, + "hide": "hideVariable", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_properties_var", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v0alpha1.json new file mode 100644 index 00000000000..4033b0f297f --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v0alpha1.json @@ -0,0 +1,187 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v13.graph_thresholds.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "threshold1": 200, + "threshold1Color": "yellow", + "threshold2": 400, + "threshold2Color": "red", + "thresholdLine": true + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Line Thresholds", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "threshold1": 100, + "threshold1Color": "green", + "threshold2": 300, + "threshold2Color": "blue", + "thresholdLine": false + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Fill Thresholds", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "threshold1": 150, + "threshold1Color": "orange", + "thresholdLine": true + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Single Threshold", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "threshold1": 200, + "threshold1Color": "yellow", + "thresholdLine": false + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "thresholds": [ + { + "color": "purple", + "colorMode": "custom", + "value": 50 + } + ], + "title": "Graph with Existing Thresholds", + "type": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Non-Graph Panel", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V13 Graph Thresholds Migration Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json new file mode 100644 index 00000000000..49108c4b128 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json @@ -0,0 +1,355 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v13.graph_thresholds.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Graph with Line Thresholds", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Graph with Fill Thresholds", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Graph with Single Threshold", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Graph with Existing Thresholds", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Non-Graph Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V13 Graph Thresholds Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json new file mode 100644 index 00000000000..a41d04804b7 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json @@ -0,0 +1,366 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v13.graph_thresholds.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Graph with Line Thresholds", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Graph with Fill Thresholds", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Graph with Single Threshold", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Graph with Existing Thresholds", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Non-Graph Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V13 Graph Thresholds Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v0alpha1.json new file mode 100644 index 00000000000..691e08a7ef5 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v0alpha1.json @@ -0,0 +1,74 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v13.minimal_graph_config.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "id": 4, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "editorMode": "builder", + "expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Dashboard with minimal graph panel settings", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json new file mode 100644 index 00000000000..c0ab55dcacf --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json @@ -0,0 +1,133 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v13.minimal_graph_config.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}", + "instant": false, + "legendFormat": "__auto", + "range": true + } + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Dashboard with minimal graph panel settings", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json new file mode 100644 index 00000000000..8e18ac60c4d --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json @@ -0,0 +1,136 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v13.minimal_graph_config.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "gdev-prometheus" + }, + "spec": { + "editorMode": "builder", + "expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}", + "instant": false, + "legendFormat": "__auto", + "range": true + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Dashboard with minimal graph panel settings", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v0alpha1.json new file mode 100644 index 00000000000..5dca5e74aaa --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v0alpha1.json @@ -0,0 +1,129 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v14.shared_crosshair_to_graph_tooltip.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage", + "refId": "A" + } + ], + "title": "CPU Usage Over Time", + "type": "timeseries", + "yAxes": [ + { + "show": true + } + ] + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "memory_usage", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "timeseries", + "yAxes": [ + { + "max": 100, + "min": 0, + "show": true + } + ] + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V14 Shared Crosshair Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json new file mode 100644 index 00000000000..50d3e9aff2f --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json @@ -0,0 +1,214 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v14.shared_crosshair_to_graph_tooltip.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Crosshair", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage Over Time", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "cpu_usage" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "memory_usage" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-1h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V14 Shared Crosshair Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json new file mode 100644 index 00000000000..8ee98acf062 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json @@ -0,0 +1,221 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v14.shared_crosshair_to_graph_tooltip.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Crosshair", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage Over Time", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "cpu_usage" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "memory_usage" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-1h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V14 Shared Crosshair Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json new file mode 100644 index 00000000000..6c259253e55 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json @@ -0,0 +1,813 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v15.mimir_rollout_debugging.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "uid": "$datasource" + }, + "enable": true, + "expr": "up", + "hide": false, + "iconColor": "rgba(255, 96, 96, 1)", + "name": "rollouts", + "showIn": 0, + "tags": [], + "titleFormat": "Rollout was underway in {{cluster}}/{{namespace}}", + "type": "tags" + } + ] + }, + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [ + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "show-in-mimir-links-dropdown" + ], + "targetBlank": false, + "title": "Mimir dashboards", + "type": "dashboards" + } + ], + "panels": [ + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 19, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 7, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Rollout progress", + "type": "row" + }, + { + "datasource": { + "uid": "$datasource" + }, + "description": "### Versions running\nShows the versions reported by each running pod.\n\nThe rollout will fail if any pod is not running the expected version.\n\nPods in green are running the expected version, while pods running other versions are shown in orange.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "orange", + "mode": "shades" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*(target)/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "percent", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xField": "job\\version", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": { + "uid": "$datasource" + }, + "expr": "sum by (job, version) (up{job=~\".*\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Versions running", + "transformations": [ + { + "id": "groupingToMatrix", + "options": { + "columnField": "version", + "rowField": "job", + "valueField": "Value" + } + }, + { + "id": "sortBy", + "options": { + "sort": [ + { + "field": "job\\version" + } + ] + } + } + ], + "type": "barchart" + }, + { + "datasource": { + "uid": "$datasource" + }, + "description": "### Deployment rollout progress\nShows the number of pods for each `Deployment` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `Deployment`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange" + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": { + "uid": "$datasource" + }, + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Deployment rollout progress", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + ], + "type": "barchart" + }, + { + "datasource": { + "uid": "$datasource" + }, + "description": "### StatefulSet rollout progress\nShows the number of pods for each `StatefulSet` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `StatefulSet`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange" + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 3, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": { + "uid": "$datasource" + }, + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "StatefulSet rollout progress", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + ], + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 19, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 8, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Rollout health", + "type": "row" + }, + { + "datasource": { + "uid": "$datasource" + }, + "description": "### Aggregator lag\nShows the consumption lag of each aggregator pod.\n\nThis panel may show no data if aggregators are not deployed to this cell.\n\nThe rollout will fail if any pod's consumption lag is both:\n* greater than 30s (red area on graph), and\n* trending upwards compared to 1 minute earlier\n\n", + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "fillOpacity": 0, + "lineWidth": 1, + "pointSize": 5, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "min": 0, + "noValue": "No data (are aggregators deployed in this cell?)", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 30 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 0, + "y": 21 + }, + "id": 4, + "options": { + "legend": { + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "$datasource" + }, + "expr": "max by (pod) (up{job=\"mimir-aggregator\"})", + "format": "time_series", + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Aggregator lag", + "type": "timeseries" + }, + { + "datasource": { + "uid": "$datasource" + }, + "description": "### Unhealthy Deployment replicas\nShows the number of unavailable pods for each `Deployment`.\n\nThe rollout will fail if any `Deployment` has an unavailable pod.\n\nBoth this panel and the rollout check ignore any `Deployment`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`Deployment`s shown in green do not have any unavailable pods, while `Deployment`s shown in orange have one or more unavailable pods.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "orange", + "value": 1 + } + ] + }, + "unit": "" + }, + "overrides": [] + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 8, + "y": 21 + }, + "id": 5, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": { + "uid": "$datasource" + }, + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Unhealthy Deployment replicas", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + ], + "type": "barchart" + }, + { + "datasource": { + "uid": "$datasource" + }, + "description": "### Unhealthy StatefulSet replicas\nShows the number of pods for each `StatefulSet` that are not ready.\n\nThe rollout will fail if any `StatefulSet` has fewer ready pods than requested.\n\nBoth this panel and the rollout check ignore any `StatefulSets`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`StatefulSets`s shown in green do not have any pods that are not ready, while `StatefulSet`s shown in orange have one or more pods that are not ready.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "orange", + "value": 1 + } + ] + }, + "unit": "" + }, + "overrides": [] + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 16, + "y": 21 + }, + "id": 6, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": { + "uid": "$datasource" + }, + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Unhealthy StatefulSet replicas", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + ], + "type": "barchart" + } + ], + "refresh": "5m", + "schemaVersion": 42, + "tags": [ + "mimir", + "betterops-mimir", + "show-in-mimir-links-dropdown", + "as-code" + ], + "templating": { + "list": [ + { + "current": { + "value": "grafanacloud-prom" + }, + "hide": 0, + "label": "Data source", + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "allValue": ".*", + "current": { + "text": "prod", + "value": "prod" + }, + "datasource": "$datasource", + "hide": 0, + "includeAll": true, + "label": "cluster", + "multi": false, + "name": "cluster", + "options": [], + "query": "label_values(up, job)", + "refresh": 1, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "text": "prod", + "value": "prod" + }, + "datasource": "$datasource", + "hide": 0, + "includeAll": false, + "label": "namespace", + "multi": false, + "name": "namespace", + "options": [], + "query": "label_values(up{job=~\"$cluster\"}, instance)", + "refresh": 1, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Mimir / Rollout debugging", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json new file mode 100644 index 00000000000..5add17319b1 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json @@ -0,0 +1,1008 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v15.mimir_rollout_debugging.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "rgba(255, 96, 96, 1)", + "name": "rollouts", + "legacyOptions": { + "expr": "up", + "showIn": 0, + "tags": [], + "titleFormat": "Rollout was underway in {{cluster}}/{{namespace}}", + "type": "tags" + } + } + } + ], + "cursorSync": "Crosshair", + "editable": false, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Versions running", + "description": "### Versions running\nShows the versions reported by each running pod.\n\nThe rollout will fail if any pod is not running the expected version.\n\nPods in green are running the expected version, while pods running other versions are shown in orange.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum by (job, version) (up{job=~\".*\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "datasource": { + "type": "", + "uid": "$datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "groupingToMatrix", + "spec": { + "id": "groupingToMatrix", + "options": { + "columnField": "version", + "rowField": "job", + "valueField": "Value" + } + } + }, + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "sort": [ + { + "field": "job\\version" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "percent", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xField": "job\\version", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "min": 0, + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [] + }, + "color": { + "mode": "shades", + "fixedColor": "orange" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*(target)/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Deployment rollout progress", + "description": "### Deployment rollout progress\nShows the number of pods for each `Deployment` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `Deployment`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "datasource": { + "type": "", + "uid": "$datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "min": 0, + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "orange" + }, + { + "value": 1, + "color": "green" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "StatefulSet rollout progress", + "description": "### StatefulSet rollout progress\nShows the number of pods for each `StatefulSet` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `StatefulSet`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "datasource": { + "type": "", + "uid": "$datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "min": 0, + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "orange" + }, + { + "value": 1, + "color": "green" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Aggregator lag", + "description": "### Aggregator lag\nShows the consumption lag of each aggregator pod.\n\nThis panel may show no data if aggregators are not deployed to this cell.\n\nThe rollout will fail if any pod's consumption lag is both:\n* greater than 30s (red area on graph), and\n* trending upwards compared to 1 minute earlier\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max by (pod) (up{job=\"mimir-aggregator\"})", + "format": "time_series", + "legendFormat": "__auto" + } + }, + "datasource": { + "type": "", + "uid": "$datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 30, + "color": "red" + } + ] + }, + "noValue": "No data (are aggregators deployed in this cell?)", + "custom": { + "drawStyle": "line", + "fillOpacity": 0, + "lineWidth": 1, + "pointSize": 5, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Unhealthy Deployment replicas", + "description": "### Unhealthy Deployment replicas\nShows the number of unavailable pods for each `Deployment`.\n\nThe rollout will fail if any `Deployment` has an unavailable pod.\n\nBoth this panel and the rollout check ignore any `Deployment`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`Deployment`s shown in green do not have any unavailable pods, while `Deployment`s shown in orange have one or more unavailable pods.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "datasource": { + "type": "", + "uid": "$datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 1, + "color": "orange" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Unhealthy StatefulSet replicas", + "description": "### Unhealthy StatefulSet replicas\nShows the number of pods for each `StatefulSet` that are not ready.\n\nThe rollout will fail if any `StatefulSet` has fewer ready pods than requested.\n\nBoth this panel and the rollout check ignore any `StatefulSets`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`StatefulSets`s shown in green do not have any pods that are not ready, while `StatefulSet`s shown in orange have one or more pods that are not ready.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "datasource": { + "type": "", + "uid": "$datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 1, + "color": "orange" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Rollout progress", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Rollout health", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [ + { + "title": "Mimir dashboards", + "type": "dashboards", + "icon": "external link", + "tooltip": "", + "tags": [ + "show-in-mimir-links-dropdown" + ], + "asDropdown": true, + "targetBlank": false, + "includeVars": true, + "keepTime": true + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "mimir", + "betterops-mimir", + "show-in-mimir-links-dropdown", + "as-code" + ], + "timeSettings": { + "timezone": "utc", + "from": "now-1h", + "to": "now", + "autoRefresh": "5m", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Mimir / Rollout debugging", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "grafanacloud-prom" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Data source", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "cluster", + "current": { + "text": "prod", + "value": "prod" + }, + "label": "cluster", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, job)" + } + }, + "regex": "", + "sort": "alphabeticalAsc", + "options": [], + "multi": false, + "includeAll": true, + "allValue": ".*", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "namespace", + "current": { + "text": "prod", + "value": "prod" + }, + "label": "namespace", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" + } + }, + "regex": "", + "sort": "alphabeticalAsc", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json new file mode 100644 index 00000000000..709c7f5f422 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json @@ -0,0 +1,1027 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v15.mimir_rollout_debugging.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "rgba(255, 96, 96, 1)", + "name": "rollouts", + "legacyOptions": { + "expr": "up", + "showIn": 0, + "tags": [], + "titleFormat": "Rollout was underway in {{cluster}}/{{namespace}}", + "type": "tags" + } + } + } + ], + "cursorSync": "Crosshair", + "editable": false, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Versions running", + "description": "### Versions running\nShows the versions reported by each running pod.\n\nThe rollout will fail if any pod is not running the expected version.\n\nPods in green are running the expected version, while pods running other versions are shown in orange.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "$datasource" + }, + "spec": { + "expr": "sum by (job, version) (up{job=~\".*\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "groupingToMatrix", + "spec": { + "id": "groupingToMatrix", + "options": { + "columnField": "version", + "rowField": "job", + "valueField": "Value" + } + } + }, + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "sort": [ + { + "field": "job\\version" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "percent", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xField": "job\\version", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "min": 0, + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [] + }, + "color": { + "mode": "shades", + "fixedColor": "orange" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*(target)/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Deployment rollout progress", + "description": "### Deployment rollout progress\nShows the number of pods for each `Deployment` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `Deployment`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "$datasource" + }, + "spec": { + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "min": 0, + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "orange" + }, + { + "value": 1, + "color": "green" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "StatefulSet rollout progress", + "description": "### StatefulSet rollout progress\nShows the number of pods for each `StatefulSet` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `StatefulSet`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "$datasource" + }, + "spec": { + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "min": 0, + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "orange" + }, + { + "value": 1, + "color": "green" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Aggregator lag", + "description": "### Aggregator lag\nShows the consumption lag of each aggregator pod.\n\nThis panel may show no data if aggregators are not deployed to this cell.\n\nThe rollout will fail if any pod's consumption lag is both:\n* greater than 30s (red area on graph), and\n* trending upwards compared to 1 minute earlier\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "$datasource" + }, + "spec": { + "expr": "max by (pod) (up{job=\"mimir-aggregator\"})", + "format": "time_series", + "legendFormat": "__auto" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 30, + "color": "red" + } + ] + }, + "noValue": "No data (are aggregators deployed in this cell?)", + "custom": { + "drawStyle": "line", + "fillOpacity": 0, + "lineWidth": 1, + "pointSize": 5, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Unhealthy Deployment replicas", + "description": "### Unhealthy Deployment replicas\nShows the number of unavailable pods for each `Deployment`.\n\nThe rollout will fail if any `Deployment` has an unavailable pod.\n\nBoth this panel and the rollout check ignore any `Deployment`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`Deployment`s shown in green do not have any unavailable pods, while `Deployment`s shown in orange have one or more unavailable pods.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "$datasource" + }, + "spec": { + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 1, + "color": "orange" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Unhealthy StatefulSet replicas", + "description": "### Unhealthy StatefulSet replicas\nShows the number of pods for each `StatefulSet` that are not ready.\n\nThe rollout will fail if any `StatefulSet` has fewer ready pods than requested.\n\nBoth this panel and the rollout check ignore any `StatefulSets`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`StatefulSets`s shown in green do not have any pods that are not ready, while `StatefulSet`s shown in orange have one or more pods that are not ready.\n\n", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "$datasource" + }, + "spec": { + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 1, + "color": "orange" + } + ] + }, + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Rollout progress", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Rollout health", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 19, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [ + { + "title": "Mimir dashboards", + "type": "dashboards", + "icon": "external link", + "tooltip": "", + "tags": [ + "show-in-mimir-links-dropdown" + ], + "asDropdown": true, + "targetBlank": false, + "includeVars": true, + "keepTime": true + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "mimir", + "betterops-mimir", + "show-in-mimir-links-dropdown", + "as-code" + ], + "timeSettings": { + "timezone": "utc", + "from": "now-1h", + "to": "now", + "autoRefresh": "5m", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Mimir / Rollout debugging", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "grafanacloud-prom" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Data source", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "cluster", + "current": { + "text": "prod", + "value": "prod" + }, + "label": "cluster", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(up, job)" + } + }, + "regex": "", + "sort": "alphabeticalAsc", + "options": [], + "multi": false, + "includeAll": true, + "allValue": ".*", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "namespace", + "current": { + "text": "prod", + "value": "prod" + }, + "label": "namespace", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" + } + }, + "regex": "", + "sort": "alphabeticalAsc", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v0alpha1.json new file mode 100644 index 00000000000..2b5625047da --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v0alpha1.json @@ -0,0 +1,135 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v15.no-op-migration.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "grafana" + }, + "enable": true, + "name": "Annotations \u0026 Alerts" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries", + "yAxes": [ + { + "show": true + } + ] + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V15 No-Op Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json new file mode 100644 index 00000000000..34d2ad77891 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json @@ -0,0 +1,227 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v15.no-op-migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "grafana" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Annotations \u0026 Alerts" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V15 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json new file mode 100644 index 00000000000..a2a031d5fa0 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json @@ -0,0 +1,235 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v15.no-op-migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "grafana" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Annotations \u0026 Alerts" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V15 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v0alpha1.json new file mode 100644 index 00000000000..649959487c2 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v0alpha1.json @@ -0,0 +1,1090 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v16.empty-rows-and-panels-array.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "Sample monitoring dashboard for testing purposes.", + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 36098, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "sample-monitoring" + ], + "targetBlank": false, + "title": "Related dashboards", + "type": "dashboards", + "url": "" + } + ], + "panels": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample metric showing connection count.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "random_metric_alpha{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Alpha metric", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample counter metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "rate(sample_counter_beta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Beta counter", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample error metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "increase(test_errors_gamma{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Gamma errors", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample event rate metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "rate(demo_events_delta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Delta events", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample resource utilization metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "example_usage_epsilon{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{status}}", + "refId": "A" + } + ], + "title": "Epsilon usage", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample memory allocation metrics.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 0, + "y": 24 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - total", + "refId": "A" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "sample_target_eta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - target", + "refId": "B" + } + ], + "title": "Zeta and Eta", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample utilization percentage.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 24 + }, + "id": 8, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.1.7", + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "100 * random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"} / clamp_min(test_available_theta{env=~\"$env\", region=~\"$region\", node=~\"$node\"},1)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Theta utilization", + "type": "gauge" + }, + { + "collapsed": false, + "datasource": { + "uid": "${example_datasource}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 9, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "refId": "A" + } + ], + "title": "Sample section", + "type": "row" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample latency metric for primary operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 41 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"primary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Iota primary latency", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample latency metric for secondary operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 41 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"secondary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Iota secondary latency", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${example_datasource}" + }, + "description": "Sample expansion events metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 49 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "uid": "${example_datasource}" + }, + "expr": "increase(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Kappa expansions", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 42, + "tags": [ + "sample-monitoring" + ], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "label": "Data Source", + "name": "example_datasource", + "options": [], + "query": "sample_source", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "allValue": ".+", + "current": {}, + "datasource": { + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Environment", + "multi": true, + "name": "env", + "options": [], + "query": "label_values(system_info_lambda{}, env)", + "refresh": 2, + "regex": "", + "sort": 2, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Region", + "multi": true, + "name": "region", + "options": [], + "query": "label_values(system_info_lambda{env=~\"$env\"}, region)", + "refresh": 2, + "regex": "", + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".+", + "current": {}, + "datasource": { + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Node", + "multi": true, + "name": "node", + "options": [], + "query": "label_values(system_info_lambda{env=~\"$env\", region=~\"$region\"}, node)", + "refresh": 2, + "regex": "", + "sort": 2, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".+", + "current": {}, + "datasource": { + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Service", + "multi": true, + "name": "service", + "options": [], + "query": "label_values(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\"}, service)", + "refresh": 2, + "regex": "", + "sort": 2, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "default", + "title": "Sample dashboard", + "uid": "sample-dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json new file mode 100644 index 00000000000..f94cd8566b2 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json @@ -0,0 +1,1459 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v16.empty-rows-and-panels-array.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "description": "Sample monitoring dashboard for testing purposes.", + "editable": false, + "elements": { + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Iota primary latency", + "description": "Sample latency metric for primary operations.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"primary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Iota secondary latency", + "description": "Sample latency metric for secondary operations.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"secondary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Kappa expansions", + "description": "Sample expansion events metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "increase(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Alpha metric", + "description": "Sample metric showing connection count.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "random_metric_alpha{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Beta counter", + "description": "Sample counter metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "rate(sample_counter_beta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "ops", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Gamma errors", + "description": "Sample error metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "increase(test_errors_gamma{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Delta events", + "description": "Sample event rate metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "rate(demo_events_delta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Epsilon usage", + "description": "Sample resource utilization metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "example_usage_epsilon{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{status}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "bytes", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Zeta and Eta", + "description": "Sample memory allocation metrics.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - total" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sample_target_eta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - target" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "bytes", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Theta utilization", + "description": "Sample utilization percentage.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "100 * random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"} / clamp_min(test_available_theta{env=~\"$env\", region=~\"$region\", node=~\"$node\"},1)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "9.1.7", + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "fieldConfig": { + "defaults": { + "unit": "percent", + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "thresholds" + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 16, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 24, + "width": 16, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 24, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Sample section", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [ + { + "title": "Related dashboards", + "type": "dashboards", + "icon": "external link", + "tooltip": "", + "url": "", + "tags": [ + "sample-monitoring" + ], + "asDropdown": false, + "targetBlank": false, + "includeVars": true, + "keepTime": true + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "sample-monitoring" + ], + "timeSettings": { + "timezone": "default", + "from": "now-30m", + "to": "now", + "autoRefresh": "30s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Sample dashboard", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "example_datasource", + "pluginId": "sample_source", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Data Source", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "env", + "current": { + "text": "", + "value": "" + }, + "label": "Environment", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(system_info_lambda{}, env)" + } + }, + "regex": "", + "sort": "alphabeticalDesc", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".+", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "region", + "current": { + "text": "", + "value": "" + }, + "label": "Region", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(system_info_lambda{env=~\"$env\"}, region)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".*", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "node", + "current": { + "text": "", + "value": "" + }, + "label": "Node", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(system_info_lambda{env=~\"$env\", region=~\"$region\"}, node)" + } + }, + "regex": "", + "sort": "alphabeticalDesc", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".+", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "service", + "current": { + "text": "", + "value": "" + }, + "label": "Service", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\"}, service)" + } + }, + "regex": "", + "sort": "alphabeticalDesc", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".+", + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json new file mode 100644 index 00000000000..d7f5fd82659 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json @@ -0,0 +1,1485 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v16.empty-rows-and-panels-array.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "description": "Sample monitoring dashboard for testing purposes.", + "editable": false, + "elements": { + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Iota primary latency", + "description": "Sample latency metric for primary operations.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"primary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Iota secondary latency", + "description": "Sample latency metric for secondary operations.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"secondary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Kappa expansions", + "description": "Sample expansion events metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "increase(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Alpha metric", + "description": "Sample metric showing connection count.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "random_metric_alpha{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Beta counter", + "description": "Sample counter metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "rate(sample_counter_beta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "ops", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Gamma errors", + "description": "Sample error metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "increase(test_errors_gamma{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Delta events", + "description": "Sample event rate metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "rate(demo_events_delta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Epsilon usage", + "description": "Sample resource utilization metric.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "example_usage_epsilon{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{status}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "bytes", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Zeta and Eta", + "description": "Sample memory allocation metrics.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - total" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "sample_target_eta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - target" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "bytes", + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Theta utilization", + "description": "Sample utilization percentage.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "expr": "100 * random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"} / clamp_min(test_available_theta{env=~\"$env\", region=~\"$region\", node=~\"$node\"},1)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "9.1.7", + "spec": { + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "fieldConfig": { + "defaults": { + "unit": "percent", + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + } + ] + }, + "color": { + "mode": "thresholds" + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 16, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 24, + "width": 16, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 24, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Sample section", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [ + { + "title": "Related dashboards", + "type": "dashboards", + "icon": "external link", + "tooltip": "", + "url": "", + "tags": [ + "sample-monitoring" + ], + "asDropdown": false, + "targetBlank": false, + "includeVars": true, + "keepTime": true + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "sample-monitoring" + ], + "timeSettings": { + "timezone": "default", + "from": "now-30m", + "to": "now", + "autoRefresh": "30s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Sample dashboard", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "example_datasource", + "pluginId": "sample_source", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Data Source", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "env", + "current": { + "text": "", + "value": "" + }, + "label": "Environment", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "__legacyStringValue": "label_values(system_info_lambda{}, env)" + } + }, + "regex": "", + "sort": "alphabeticalDesc", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".+", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "region", + "current": { + "text": "", + "value": "" + }, + "label": "Region", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "__legacyStringValue": "label_values(system_info_lambda{env=~\"$env\"}, region)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".*", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "node", + "current": { + "text": "", + "value": "" + }, + "label": "Node", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "__legacyStringValue": "label_values(system_info_lambda{env=~\"$env\", region=~\"$region\"}, node)" + } + }, + "regex": "", + "sort": "alphabeticalDesc", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".+", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "service", + "current": { + "text": "", + "value": "" + }, + "label": "Service", + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "datasource": { + "name": "${example_datasource}" + }, + "spec": { + "__legacyStringValue": "label_values(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\"}, service)" + } + }, + "regex": "", + "sort": "alphabeticalDesc", + "options": [], + "multi": true, + "includeAll": true, + "allValue": ".+", + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v0alpha1.json new file mode 100644 index 00000000000..b32592a5151 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v0alpha1.json @@ -0,0 +1,437 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v16.grid_layout_upgrade.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 13, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "row" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + }, + { + "collapsed": true, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 14, + "panels": [ + { + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 3, + "title": "Process List", + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 17 + }, + "height": 200, + "id": 4, + "title": "Network I/O", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 17 + }, + "height": 200, + "id": 5, + "title": "Disk I/O", + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 15, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Visible Row Title", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 6, + "maxPerRow": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Temperature", + "type": "gauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 18 + }, + "id": 7, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 18 + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Load Average", + "type": "bargauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 16, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 25 + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Description Panel", + "type": "text" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 25 + }, + "height": 100, + "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "System Logs", + "type": "logs" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 17, + "panels": [], + "repeat": "server", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "row" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 11, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Server Metrics", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V16 Grid Layout Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json new file mode 100644 index 00000000000..c17e76fbc77 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json @@ -0,0 +1,723 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v16.grid_layout_upgrade.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "System Logs", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "logs", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Server Metrics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Process List", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Network I/O", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Disk I/O", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Temperature", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Uptime", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Load Average", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "bargauge", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Description Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 12, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 12, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Visible Row Title", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 16, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "repeat": { + "mode": "variable", + "value": "server" + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V16 Grid Layout Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json new file mode 100644 index 00000000000..2a6622f27a4 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json @@ -0,0 +1,743 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v16.grid_layout_upgrade.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "System Logs", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "logs", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Server Metrics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Process List", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Network I/O", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Disk I/O", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Temperature", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Uptime", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Load Average", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "bargauge", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Description Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 12, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 12, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Visible Row Title", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 16, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "repeat": { + "mode": "variable", + "value": "server" + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V16 Grid Layout Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v0alpha1.json new file mode 100644 index 00000000000..047f8ec917b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v0alpha1.json @@ -0,0 +1,894 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v16.span_zero_demo.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "icon": "external link", + "targetBlank": true, + "title": "External Documentation", + "type": "link", + "url": "https://example.com/docs" + } + ], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Application Monitoring", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 23, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Application Service", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 6, + "options": { + "content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Service Overview", + "type": "text" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 7, + "options": { + "content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Error Monitoring", + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "yellow", + "value": 0.95 + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 8, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n", + "legendFormat": "{{action}}", + "refId": "A" + } + ], + "title": "Job Success Rate", + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 9, + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"", + "refId": "A" + } + ], + "title": "Errors", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 10, + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt", + "refId": "A" + } + ], + "title": "All", + "type": "logs" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 8 + }, + "id": 11, + "options": { + "content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Performance Analysis", + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "description": "Number of concurrent processing threads available for handling operations", + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 15 + }, + "id": 12, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})", + "instant": true, + "refId": "A" + } + ], + "title": "Concurrent Job Drivers", + "type": "stat" + }, + { + "datasource": { + "type": "tempo", + "uid": "${tempo}" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 15 + }, + "id": 13, + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "${tempo}" + }, + "filters": [ + { + "id": "span-name", + "operator": "=", + "scope": "span", + "tag": "name", + "value": [ + "provisioning.sync.process" + ] + }, + { + "id": "k8s-cluster-name", + "operator": "=", + "scope": "resource", + "tag": "k8s.cluster.name", + "value": [ + "$cluster" + ] + } + ], + "query": "{name=\"app.operation.process\"}", + "queryType": "traceqlSearch", + "refId": "A" + } + ], + "title": "Recent Operation Traces", + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 2 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 15 + }, + "id": 14, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}", + "refId": "E" + } + ], + "timeFrom": "7d", + "title": "7d avg of job durations", + "transformations": [ + { + "id": "reduce", + "options": { + "mode": "seriesToRows", + "reducers": [ + "mean" + ] + } + }, + { + "id": "seriesToRows" + }, + { + "id": "organize", + "options": { + "renameByName": { + "Field": "Type", + "Mean": "Avg Duration", + "Metric": "Legend", + "Value": "Duration" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 22 + }, + "id": 15, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}", + "refId": "E" + } + ], + "title": "Job Duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "description": "Total number of jobs waiting to be processed", + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 22 + }, + "id": 16, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)", + "legendFormat": "Queue size", + "refId": "A" + } + ], + "title": "Queue Size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "fieldConfig": { + "defaults": { + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 22 + }, + "id": 17, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))", + "legendFormat": "Queue size", + "refId": "A" + } + ], + "timeFrom": "7d", + "title": "7d avg Queue Wait Time", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "description": "How long a job is in the queue before being picked up", + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 29 + }, + "id": 18, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.99", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.95", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.5", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.1", + "refId": "E" + } + ], + "title": "Queue Wait Time", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 29 + }, + "id": 19, + "options": { + "content": "Resource utilization monitoring for application containers", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Resource Monitoring", + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 29 + }, + "id": 20, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))", + "legendFormat": "{{cluster}}", + "refId": "A" + } + ], + "title": "Running Pod(s)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 36 + }, + "id": 21, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Request", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Limit", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)", + "legendFormat": "Container usage {{pod}}", + "refId": "C" + } + ], + "title": "Memory Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 36 + }, + "id": 22, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)", + "legendFormat": "Usage {{pod}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)", + "legendFormat": "Throttling {{pod}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU limit", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU request", + "refId": "D" + } + ], + "title": "CPU Utilization", + "type": "timeseries" + } + ], + "refresh": "10s", + "schemaVersion": 42, + "tags": [ + "as-code" + ], + "templating": { + "list": [ + { + "current": { + "value": "prometheus-datasource" + }, + "hide": 0, + "label": "Data source", + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": { + "value": "prometheus-datasource" + }, + "name": "prom", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": { + "value": "loki-datasource" + }, + "name": "loki", + "options": [], + "query": "loki", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": { + "text": "tempo-datasource", + "value": "tempo-datasource" + }, + "name": "tempo", + "options": [], + "query": "tempo", + "refresh": 1, + "regex": ".*tempo.*", + "type": "datasource" + }, + { + "current": { + "text": "demo-cluster", + "value": "demo-cluster" + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "name": "cluster", + "options": [], + "query": "label_values(app_worker_threads_active,cluster)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Span Zero Demo Dashboard", + "uid": "span-zero-demo-dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json new file mode 100644 index 00000000000..c8699b49e46 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json @@ -0,0 +1,1659 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v16.span_zero_demo.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": false, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Application Monitoring", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "All", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt" + } + }, + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "logs", + "spec": { + "pluginVersion": "", + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Performance Analysis", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Concurrent Job Drivers", + "description": "Number of concurrent processing threads available for handling operations", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})", + "instant": true + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Recent Operation Traces", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "filters": [ + { + "id": "span-name", + "operator": "=", + "scope": "span", + "tag": "name", + "value": [ + "provisioning.sync.process" + ] + }, + { + "id": "k8s-cluster-name", + "operator": "=", + "scope": "resource", + "tag": "k8s.cluster.name", + "value": [ + "$cluster" + ] + } + ], + "query": "{name=\"app.operation.process\"}", + "queryType": "traceqlSearch" + } + }, + "datasource": { + "type": "tempo", + "uid": "${tempo}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "7d avg of job durations", + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "mode": "seriesToRows", + "reducers": [ + "mean" + ] + } + } + }, + { + "kind": "seriesToRows", + "spec": { + "id": "seriesToRows", + "options": null + } + }, + { + "kind": "organize", + "spec": { + "id": "organize", + "options": { + "renameByName": { + "Field": "Type", + "Mean": "Avg Duration", + "Metric": "Legend", + "Value": "Duration" + } + } + } + } + ], + "queryOptions": { + "timeFrom": "7d" + } + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 2, + "color": "yellow" + }, + { + "value": 5, + "color": "red" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-15": { + "kind": "Panel", + "spec": { + "id": 15, + "title": "Job Duration", + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-16": { + "kind": "Panel", + "spec": { + "id": 16, + "title": "Queue Size", + "description": "Total number of jobs waiting to be processed", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)", + "legendFormat": "Queue size" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-17": { + "kind": "Panel", + "spec": { + "id": 17, + "title": "7d avg Queue Wait Time", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))", + "legendFormat": "Queue size" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "timeFrom": "7d" + } + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "s" + }, + "overrides": [] + } + } + } + } + }, + "panel-18": { + "kind": "Panel", + "spec": { + "id": 18, + "title": "Queue Wait Time", + "description": "How long a job is in the queue before being picked up", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.99" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.95" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.5" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.1" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-19": { + "kind": "Panel", + "spec": { + "id": 19, + "title": "Resource Monitoring", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "Resource utilization monitoring for application containers", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-20": { + "kind": "Panel", + "spec": { + "id": 20, + "title": "Running Pod(s)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))", + "legendFormat": "{{cluster}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-21": { + "kind": "Panel", + "spec": { + "id": 21, + "title": "Memory Utilization", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Request" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Limit" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)", + "legendFormat": "Container usage {{pod}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-22": { + "kind": "Panel", + "spec": { + "id": 22, + "title": "CPU Utilization", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)", + "legendFormat": "Usage {{pod}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)", + "legendFormat": "Throttling {{pod}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU limit" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU request" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Service Overview", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Error Monitoring", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Job Success Rate", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n", + "legendFormat": "{{action}}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "red" + }, + { + "value": 0.95, + "color": "yellow" + }, + { + "value": 1, + "color": "green" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Errors", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"" + } + }, + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "logs", + "spec": { + "pluginVersion": "", + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Application Service", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 7, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 7, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 7, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 14, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 14, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 14, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 21, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-15" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 21, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-16" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 21, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-17" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 28, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-18" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 28, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-19" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 28, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-20" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 35, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-21" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 35, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-22" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [ + { + "title": "External Documentation", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "https://example.com/docs", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "as-code" + ], + "timeSettings": { + "timezone": "utc", + "from": "now-6h", + "to": "now", + "autoRefresh": "10s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Span Zero Demo Dashboard", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "prometheus-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Data source", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "prom", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "prometheus-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "loki", + "pluginId": "loki", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "loki-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "tempo", + "pluginId": "tempo", + "refresh": "onDashboardLoad", + "regex": ".*tempo.*", + "current": { + "text": "tempo-datasource", + "value": "tempo-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "cluster", + "current": { + "text": "demo-cluster", + "value": "demo-cluster" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(app_worker_threads_active,cluster)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json new file mode 100644 index 00000000000..7c05e2ab74c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json @@ -0,0 +1,1711 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v16.span_zero_demo.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": false, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Application Monitoring", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "All", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "${loki}" + }, + "spec": { + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "logs", + "version": "", + "spec": { + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Performance Analysis", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Concurrent Job Drivers", + "description": "Number of concurrent processing threads available for handling operations", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})", + "instant": true + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Recent Operation Traces", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "tempo", + "version": "v0", + "datasource": { + "name": "${tempo}" + }, + "spec": { + "filters": [ + { + "id": "span-name", + "operator": "=", + "scope": "span", + "tag": "name", + "value": [ + "provisioning.sync.process" + ] + }, + { + "id": "k8s-cluster-name", + "operator": "=", + "scope": "resource", + "tag": "k8s.cluster.name", + "value": [ + "$cluster" + ] + } + ], + "query": "{name=\"app.operation.process\"}", + "queryType": "traceqlSearch" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "7d avg of job durations", + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}" + } + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}" + } + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "mode": "seriesToRows", + "reducers": [ + "mean" + ] + } + } + }, + { + "kind": "seriesToRows", + "spec": { + "id": "seriesToRows", + "options": null + } + }, + { + "kind": "organize", + "spec": { + "id": "organize", + "options": { + "renameByName": { + "Field": "Type", + "Mean": "Avg Duration", + "Metric": "Legend", + "Value": "Duration" + } + } + } + } + ], + "queryOptions": { + "timeFrom": "7d" + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 2, + "color": "yellow" + }, + { + "value": 5, + "color": "red" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-15": { + "kind": "Panel", + "spec": { + "id": 15, + "title": "Job Duration", + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}" + } + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}" + } + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-16": { + "kind": "Panel", + "spec": { + "id": 16, + "title": "Queue Size", + "description": "Total number of jobs waiting to be processed", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)", + "legendFormat": "Queue size" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-17": { + "kind": "Panel", + "spec": { + "id": 17, + "title": "7d avg Queue Wait Time", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))", + "legendFormat": "Queue size" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "timeFrom": "7d" + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "s" + }, + "overrides": [] + } + } + } + } + }, + "panel-18": { + "kind": "Panel", + "spec": { + "id": 18, + "title": "Queue Wait Time", + "description": "How long a job is in the queue before being picked up", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.99" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.95" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.5" + } + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.1" + } + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-19": { + "kind": "Panel", + "spec": { + "id": 19, + "title": "Resource Monitoring", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "Resource utilization monitoring for application containers", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-20": { + "kind": "Panel", + "spec": { + "id": 20, + "title": "Running Pod(s)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))", + "legendFormat": "{{cluster}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-21": { + "kind": "Panel", + "spec": { + "id": 21, + "title": "Memory Utilization", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Request" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Limit" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)", + "legendFormat": "Container usage {{pod}}" + } + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-22": { + "kind": "Panel", + "spec": { + "id": 22, + "title": "CPU Utilization", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)", + "legendFormat": "Usage {{pod}}" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)", + "legendFormat": "Throttling {{pod}}" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU limit" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU request" + } + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Service Overview", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Error Monitoring", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Job Success Rate", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n", + "legendFormat": "{{action}}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "red" + }, + { + "value": 0.95, + "color": "yellow" + }, + { + "value": 1, + "color": "green" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Errors", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "${loki}" + }, + "spec": { + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "logs", + "version": "", + "spec": { + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 24, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Application Service", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 7, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 7, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 7, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 14, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 14, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 14, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 21, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-15" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 21, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-16" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 21, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-17" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 28, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-18" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 28, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-19" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 28, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-20" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 35, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-21" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 35, + "width": 8, + "height": 7, + "element": { + "kind": "ElementReference", + "name": "panel-22" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [ + { + "title": "External Documentation", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "https://example.com/docs", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "as-code" + ], + "timeSettings": { + "timezone": "utc", + "from": "now-6h", + "to": "now", + "autoRefresh": "10s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Span Zero Demo Dashboard", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "prometheus-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "label": "Data source", + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "prom", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "prometheus-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "loki", + "pluginId": "loki", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "", + "value": "loki-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "tempo", + "pluginId": "tempo", + "refresh": "onDashboardLoad", + "regex": ".*tempo.*", + "current": { + "text": "tempo-datasource", + "value": "tempo-datasource" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "cluster", + "current": { + "text": "demo-cluster", + "value": "demo-cluster" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "${prom}" + }, + "spec": { + "__legacyStringValue": "label_values(app_worker_threads_active,cluster)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v0alpha1.json new file mode 100644 index 00000000000..590ed8b7ed6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v0alpha1.json @@ -0,0 +1,322 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v17.minspan_to_maxperrow.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "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": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "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": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 8 + }, + "id": 2, + "maxPerRow": 6, + "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": 3, + "w": 4, + "x": 6, + "y": 8 + }, + "id": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 1", + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "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": "timeseries" + }, + { + "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": 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": 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" + }, + { + "autoMigrateFrom": "graph", + "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": "timeseries" + }, + { + "autoMigrateFrom": "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": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json new file mode 100644 index 00000000000..9534b61db24 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json @@ -0,0 +1,640 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v17.minspan_to_maxperrow.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with minSpan 8", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Panel with negative minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with minSpan 4", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with minSpan 2", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with minSpan 12", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with minSpan 24", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with minSpan 1", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel without minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with invalid minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with zero minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 6, + "y": 8, + "width": 4, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 18, + "y": 8, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 12, + "width": 24, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 18, + "width": 24, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 22, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 6, + "y": 22, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json new file mode 100644 index 00000000000..697c0dadaac --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json @@ -0,0 +1,661 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v17.minspan_to_maxperrow.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with minSpan 8", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Panel with negative minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with minSpan 4", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with minSpan 2", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with minSpan 12", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with minSpan 24", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with minSpan 1", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel without minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with invalid minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with zero minSpan", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 6, + "y": 8, + "width": 4, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 18, + "y": 8, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 12, + "width": 24, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 18, + "width": 24, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 22, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 6, + "y": 22, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v0alpha1.json new file mode 100644 index 00000000000..fbcd020bf1c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v0alpha1.json @@ -0,0 +1,207 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v18.gauge_options.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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" + }, + { + "autoMigrateFrom": "graph", + "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": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V18 Gauge Options Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json new file mode 100644 index 00000000000..7c22f7d6a45 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json @@ -0,0 +1,405 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v18.gauge_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Complete Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "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" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Partial Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": { + "valueOptions": { + "decimals": 1, + "unit": "percent" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Buggy Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": { + "valueOptions": { + "decimals": 0, + "stat": "avg", + "unit": "bytes" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Custom Properties Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": { + "anotherProp": 42, + "customProperty": "customValue", + "thresholds": [ + { + "color": "blue", + "value": 10 + } + ], + "valueOptions": { + "unit": "short" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Non-Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "show": true, + "showLegend": true + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V18 Gauge Options Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json new file mode 100644 index 00000000000..6d1528a868a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json @@ -0,0 +1,416 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v18.gauge_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Complete Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "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" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Partial Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": { + "valueOptions": { + "decimals": 1, + "unit": "percent" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Buggy Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": { + "valueOptions": { + "decimals": 0, + "stat": "avg", + "unit": "bytes" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Custom Properties Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": { + "anotherProp": 42, + "customProperty": "customValue", + "thresholds": [ + { + "color": "blue", + "value": 10 + } + ], + "valueOptions": { + "unit": "short" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Non-Gauge Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "show": true, + "showLegend": true + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V18 Gauge Options Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v0alpha1.json new file mode 100644 index 00000000000..77ad8639378 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v0alpha1.json @@ -0,0 +1,204 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v19.panel_links.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "links": [ + { + "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": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "links": [ + { + "title": "DashUri Link", + "url": "dashboard/my-dashboard-uid?$__all_variables" + } + ], + "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": [ + { + "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": [ + { + "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" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no links", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V19 Panel Links Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json new file mode 100644 index 00000000000..42f9b90a138 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json @@ -0,0 +1,438 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v19.panel_links.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with legacy dashboard link", + "description": "", + "links": [ + { + "title": "Dashboard Link", + "url": "dashboard/db/my-dashboard?$__url_time_range" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with dashUri link", + "description": "", + "links": [ + { + "title": "DashUri Link", + "url": "dashboard/my-dashboard-uid?$__all_variables" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with custom params", + "description": "", + "links": [ + { + "title": "Custom Params Link", + "url": "http://example.com?customParam=value" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with complex link", + "description": "", + "links": [ + { + "title": "Complex Link", + "url": "dashboard/db/complex-dashboard?$__url_time_range\u0026$__all_variables\u0026param1=value1\u0026param2=value2", + "targetBlank": true + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with existing URL", + "description": "", + "links": [ + { + "title": "Existing URL Link", + "url": "http://existing-url.com" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with no links", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V19 Panel Links Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json new file mode 100644 index 00000000000..0dbf12ef898 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json @@ -0,0 +1,451 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v19.panel_links.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with legacy dashboard link", + "description": "", + "links": [ + { + "title": "Dashboard Link", + "url": "dashboard/db/my-dashboard?$__url_time_range" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with dashUri link", + "description": "", + "links": [ + { + "title": "DashUri Link", + "url": "dashboard/my-dashboard-uid?$__all_variables" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with custom params", + "description": "", + "links": [ + { + "title": "Custom Params Link", + "url": "http://example.com?customParam=value" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with complex link", + "description": "", + "links": [ + { + "title": "Complex Link", + "url": "dashboard/db/complex-dashboard?$__url_time_range\u0026$__all_variables\u0026param1=value1\u0026param2=value2", + "targetBlank": true + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with existing URL", + "description": "", + "links": [ + { + "title": "Existing URL Link", + "url": "http://existing-url.com" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with no links", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V19 Panel Links Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v0alpha1.json new file mode 100644 index 00000000000..b799549c75d --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v0alpha1.json @@ -0,0 +1,181 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v2.panels-and-services.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graphite", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 100, + "min": 0 + }, + "id": 1, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "cpu.usage" + } + ], + "title": "CPU Usage", + "type": "timeseries", + "y2_format": "short", + "y_format": "percent" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "min": 0 + }, + "id": 2, + "legend": false, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "memory.usage" + } + ], + "title": "Memory Usage", + "type": "timeseries", + "y_format": "bytes" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 100, + "min": 0 + }, + "id": 3, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Server Stats", + "type": "table", + "y2_format": "bytes", + "y_format": "short" + }, + { + "autoMigrateFrom": "graphite", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "disk.io" + } + ], + "title": "Disk I/O", + "type": "timeseries", + "y2_format": "Bps" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "type": "query" + }, + { + "name": "env", + "options": [ + { + "text": "Production", + "value": "prod" + }, + { + "text": "Staging", + "value": "stage" + } + ], + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V2 Comprehensive Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json new file mode 100644 index 00000000000..2be443726d3 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json @@ -0,0 +1,358 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v2.panels-and-services.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "target": "cpu.usage" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "target": "memory.usage" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Server Stats", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Disk I/O", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "target": "disk.io" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V2 Comprehensive Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "env", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "Production", + "value": "prod" + }, + { + "selected": false, + "text": "Staging", + "value": "stage" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json new file mode 100644 index 00000000000..6a82b1603f8 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json @@ -0,0 +1,369 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v2.panels-and-services.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "target": "cpu.usage" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "target": "memory.usage" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Server Stats", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Disk I/O", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "target": "disk.io" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V2 Comprehensive Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "server", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(server)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "env", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "Production", + "value": "prod" + }, + { + "selected": false, + "text": "Staging", + "value": "stage" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v0alpha1.json new file mode 100644 index 00000000000..54910573b42 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v0alpha1.json @@ -0,0 +1,262 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v20.variable_syntax_links.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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" + } + } + }, + "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": 42, + "tags": [ + "migration-test" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V20 Variable Syntax Migration Test Dashboard", + "uid": "v20-migration-test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json new file mode 100644 index 00000000000..81ccea2ab37 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json @@ -0,0 +1,430 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v20.variable_syntax_links.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with data links using legacy variable syntax", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "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}" + } + ] + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with field options using legacy variable syntax", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "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" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with both data links and field options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "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" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with no legacy variables (should remain unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "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}" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with no data links or field options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "This panel has no data links or field options to migrate." + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 16, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 16, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "migration-test" + ], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "5s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V20 Variable Syntax Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json new file mode 100644 index 00000000000..f5c9e157c75 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json @@ -0,0 +1,441 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v20.variable_syntax_links.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with data links using legacy variable syntax", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "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}" + } + ] + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with field options using legacy variable syntax", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "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" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with both data links and field options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "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" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with no legacy variables (should remain unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "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}" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with no data links or field options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "This panel has no data links or field options to migrate." + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 16, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 16, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "migration-test" + ], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "5s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V20 Variable Syntax Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v0alpha1.json new file mode 100644 index 00000000000..f0b61436447 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v0alpha1.json @@ -0,0 +1,206 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v21.data_links_series_to_field.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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}" + } + ] + } + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with field options links", + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "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": "timeseries" + }, + { + "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": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V21 Data Links Series to Field Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json new file mode 100644 index 00000000000..32132d368c5 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json @@ -0,0 +1,405 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v21.data_links_series_to_field.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with data links", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "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}" + } + ] + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with field options links", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "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}" + } + ] + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with both link types", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "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}" + } + ] + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel without series labels", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "dataLinks": [ + { + "title": "No Series Labels Link", + "url": "http://mylink.com?other=${__field.labels}" + } + ] + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel without options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V21 Data Links Series to Field Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json new file mode 100644 index 00000000000..8c9808adf32 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json @@ -0,0 +1,416 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v21.data_links_series_to_field.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with data links", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "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}" + } + ] + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with field options links", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "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}" + } + ] + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with both link types", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "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}" + } + ] + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel without series labels", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "dataLinks": [ + { + "title": "No Series Labels Link", + "url": "http://mylink.com?other=${__field.labels}" + } + ] + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel without options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V21 Data Links Series to Field Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v0alpha1.json new file mode 100644 index 00000000000..a85ef5ebbda --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v0alpha1.json @@ -0,0 +1,83 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v22.table_panel_align.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "styles": [ + { + "align": "auto", + "pattern": "Time", + "type": "number" + }, + { + "align": "auto", + "pattern": "Value", + "type": "string" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V22 Table Panel Styles Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json new file mode 100644 index 00000000000..fbb8429153b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json @@ -0,0 +1,127 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v22.table_panel_align.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V22 Table Panel Styles Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json new file mode 100644 index 00000000000..fd3dc0b587b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json @@ -0,0 +1,130 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v22.table_panel_align.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V22 Table Panel Styles Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v0alpha1.json new file mode 100644 index 00000000000..07b650eb510 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v0alpha1.json @@ -0,0 +1,215 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v23.multi_variable_alignment.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "up", + "refId": "A" + } + ], + "title": "Test Panel", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "A" + ], + "value": [ + "A" + ] + }, + "datasource": "prometheus", + "multi": true, + "name": "multi_single_value", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "B", + "C" + ], + "value": [ + "B", + "C" + ] + }, + "datasource": "prometheus", + "multi": true, + "name": "multi_array_value", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "D", + "value": "D" + }, + "datasource": "prometheus", + "multi": false, + "name": "non_multi_array_value", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "E", + "value": "E" + }, + "datasource": "prometheus", + "multi": false, + "name": "non_multi_single_value", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "F", + "value": "F" + }, + "datasource": "prometheus", + "name": "no_multi_property", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "current": {}, + "datasource": "prometheus", + "multi": true, + "name": "empty_current", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "multi": true, + "name": "nil_current", + "options": [], + "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", + "options": [], + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "1m", + "value": "1m" + }, + "multi": false, + "name": "interval_variable", + "type": "interval" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V23 Multi Variables Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json new file mode 100644 index 00000000000..7b4fe20e68e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json @@ -0,0 +1,378 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v23.multi_variable_alignment.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Test Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "up" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V23 Multi Variables Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "multi_single_value", + "current": { + "text": [ + "A" + ], + "value": [ + "A" + ] + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "multi_array_value", + "current": { + "text": [ + "B", + "C" + ], + "value": [ + "B", + "C" + ] + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "non_multi_array_value", + "current": { + "text": "D", + "value": "D" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "non_multi_single_value", + "current": { + "text": "E", + "value": "E" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_multi_property", + "current": { + "text": "F", + "value": "F" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "empty_current", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "nil_current", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable", + "query": "", + "current": { + "text": [ + "G" + ], + "value": [ + "G" + ] + }, + "options": [], + "multi": true, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "textbox_variable", + "current": { + "text": "H", + "value": "H" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource_variable", + "pluginId": "prometheus", + "refresh": "never", + "regex": "", + "current": { + "text": [ + "Prometheus", + "InfluxDB" + ], + "value": [ + "prometheus", + "influxdb" + ] + }, + "options": [], + "multi": true, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "IntervalVariable", + "spec": { + "name": "interval_variable", + "query": "", + "current": { + "text": "1m", + "value": "1m" + }, + "options": [], + "auto": false, + "auto_min": "", + "auto_count": 0, + "refresh": "onTimeRangeChanged", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json new file mode 100644 index 00000000000..8004109e441 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json @@ -0,0 +1,395 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v23.multi_variable_alignment.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Test Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "up" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V23 Multi Variables Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "multi_single_value", + "current": { + "text": [ + "A" + ], + "value": [ + "A" + ] + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "multi_array_value", + "current": { + "text": [ + "B", + "C" + ], + "value": [ + "B", + "C" + ] + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "non_multi_array_value", + "current": { + "text": "D", + "value": "D" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "non_multi_single_value", + "current": { + "text": "E", + "value": "E" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_multi_property", + "current": { + "text": "F", + "value": "F" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "empty_current", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "nil_current", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": true, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable", + "query": "", + "current": { + "text": [ + "G" + ], + "value": [ + "G" + ] + }, + "options": [], + "multi": true, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "textbox_variable", + "current": { + "text": "H", + "value": "H" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource_variable", + "pluginId": "prometheus", + "refresh": "never", + "regex": "", + "current": { + "text": [ + "Prometheus", + "InfluxDB" + ], + "value": [ + "prometheus", + "influxdb" + ] + }, + "options": [], + "multi": true, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "IntervalVariable", + "spec": { + "name": "interval_variable", + "query": "", + "current": { + "text": "1m", + "value": "1m" + }, + "options": [], + "auto": false, + "auto_min": "", + "auto_count": 0, + "refresh": "onTimeRangeChanged", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v0alpha1.json new file mode 100644 index 00000000000..fb3ee00d757 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v0alpha1.json @@ -0,0 +1,704 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v24.table-angular.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests basic migration with default style pattern (/.*/) containing thresholds and colors. Should convert styles to fieldConfig.defaults with threshold steps.", + "id": 1, + "legend": true, + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20", + "30" + ] + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "Basic Angular Table with Defaults", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "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.", + "id": 2, + "styles": [ + { + "align": "center", + "colorMode": "cell", + "colors": [ + "green", + "yellow", + "red" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [ + "100", + "500" + ], + "unit": "bytes" + }, + { + "alias": "Current Status", + "align": "left", + "colorMode": "value", + "decimals": 0, + "pattern": "Status", + "unit": "short" + }, + { + "colorMode": "row", + "link": true, + "linkTargetBlank": true, + "linkTooltip": "View error details", + "linkUrl": "http://example.com/errors", + "pattern": "/Error.*/" + }, + { + "alias": "Timestamp", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "pattern": "Hidden", + "type": "hidden" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Complex Table with All Style Features", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "columns": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Maximum", + "value": "max" + }, + { + "text": "Minimum", + "value": "min" + }, + { + "text": "Total", + "value": "total" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Count", + "value": "count" + } + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests migration of timeseries_aggregations transform to reduce transformation with column mappings (avg-\u003emean, max-\u003emax, min-\u003emin, total-\u003esum, current-\u003elastNotNull, count-\u003ecount).", + "id": 3, + "styles": [ + { + "decimals": 1, + "pattern": "/.*/", + "unit": "percent" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Timeseries Aggregations Transform", + "transform": "timeseries_aggregations", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.", + "id": 4, + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Timeseries to Rows Transform", + "transform": "timeseries_to_rows", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests migration of timeseries_to_columns transform to seriesToColumns transformation.", + "id": 5, + "styles": [ + { + "pattern": "/.*/", + "unit": "bytes" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Timeseries to Columns Transform", + "transform": "timeseries_to_columns", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests migration of table transform to merge transformation. Also tests auto alignment conversion to empty string.", + "id": 6, + "styles": [ + { + "align": "auto", + "pattern": "/.*/" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Merge Transform", + "transform": "table", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests that existing transformations are preserved and new transformation from old format is appended to the list.", + "id": 7, + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Existing Transformations", + "transform": "timeseries_to_rows", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "field1", + "field2" + ] + } + } + } + ], + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests handling of mixed numeric and string threshold values (int, string, float) with proper type conversion.", + "id": 8, + "styles": [ + { + "colors": [ + "green", + "yellow", + "orange", + "red" + ], + "pattern": "/.*/", + "thresholds": [ + 10, + "20", + 30.5 + ] + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Mixed Threshold Types", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests all color mode mappings: cell-\u003ecolor-background, row-\u003ecolor-background, value-\u003ecolor-text.", + "id": 9, + "styles": [ + { + "colorMode": "cell", + "pattern": "/.*/" + }, + { + "colorMode": "cell", + "pattern": "CellColumn" + }, + { + "colorMode": "row", + "pattern": "RowColumn" + }, + { + "colorMode": "value", + "pattern": "ValueColumn" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "All Color Modes Test", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests all alignment options: left, center, right, and auto (should convert to empty string).", + "id": 10, + "styles": [ + { + "align": "center", + "pattern": "/.*/" + }, + { + "align": "left", + "pattern": "LeftColumn" + }, + { + "align": "right", + "pattern": "RightColumn" + }, + { + "align": "auto", + "pattern": "AutoColumn" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "All Alignment Options", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests both field matcher types: byName for exact matches and byRegexp for regex patterns.", + "id": 11, + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "alias": "Exact Match", + "pattern": "ExactColumnName" + }, + { + "alias": "Regex Match", + "pattern": "/Regex.*Pattern/" + }, + { + "alias": "Start Pattern", + "pattern": "/^Start/" + }, + { + "alias": "End Pattern", + "pattern": "/End$/" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Field Matcher Types Test", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests various link configurations: with and without tooltip, with and without target blank.", + "id": 12, + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "link": true, + "linkTargetBlank": true, + "linkTooltip": "Click to view details", + "linkUrl": "http://example.com/with-tooltip", + "pattern": "LinkWithTooltip" + }, + { + "link": true, + "linkTargetBlank": false, + "linkUrl": "http://example.com/no-tooltip", + "pattern": "LinkWithoutTooltip" + }, + { + "link": true, + "linkUrl": "http://example.com/minimal", + "pattern": "LinkMinimal" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Link Configuration Test", + "type": "table" + }, + { + "autoMigrateFrom": "table-old", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests various date format patterns and aliases.", + "id": 13, + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "alias": "ISO Date", + "dateFormat": "YYYY-MM-DD", + "pattern": "DateISO", + "type": "date" + }, + { + "alias": "Full DateTime", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "DateTime", + "type": "date" + }, + { + "alias": "Time Only", + "dateFormat": "HH:mm:ss", + "pattern": "TimeOnly", + "type": "date" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Date Format Variations", + "type": "table" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "React table (table2) should not be migrated. Properties should remain unchanged.", + "id": 14, + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20" + ], + "unit": "short" + } + ], + "table": "table2", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "React Table - Should NOT Migrate", + "type": "table" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Angular table without styles property should not be migrated.", + "id": 15, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Angular Table without Styles - Should NOT Migrate", + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Non-table panels should remain completely unchanged.", + "id": 16, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Non-Table Panel - Should NOT Migrate", + "type": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Other panel types should not be affected by table migration.", + "id": 17, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Singlestat Panel - Should NOT Migrate", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "No Title", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json new file mode 100644 index 00000000000..6226b6c15e8 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json @@ -0,0 +1,1069 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v24.table-angular.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "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.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "All Alignment Options", + "description": "Tests all alignment options: left, center, right, and auto (should convert to empty string).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Field Matcher Types Test", + "description": "Tests both field matcher types: byName for exact matches and byRegexp for regex patterns.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Link Configuration Test", + "description": "Tests various link configurations: with and without tooltip, with and without target blank.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Date Format Variations", + "description": "Tests various date format patterns and aliases.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "React Table - Should NOT Migrate", + "description": "React table (table2) should not be migrated. Properties should remain unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-15": { + "kind": "Panel", + "spec": { + "id": 15, + "title": "Angular Table without Styles - Should NOT Migrate", + "description": "Angular table without styles property should not be migrated.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-16": { + "kind": "Panel", + "spec": { + "id": 16, + "title": "Non-Table Panel - Should NOT Migrate", + "description": "Non-table panels should remain completely unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-17": { + "kind": "Panel", + "spec": { + "id": 17, + "title": "Singlestat Panel - Should NOT Migrate", + "description": "Other panel types should not be affected by table migration.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "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.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Table with Timeseries Aggregations Transform", + "description": "Tests migration of timeseries_aggregations transform to reduce transformation with column mappings (avg-\u003emean, max-\u003emax, min-\u003emin, total-\u003esum, current-\u003elastNotNull, count-\u003ecount).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Table with Timeseries to Rows Transform", + "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Table with Timeseries to Columns Transform", + "description": "Tests migration of timeseries_to_columns transform to seriesToColumns transformation.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Table with Merge Transform", + "description": "Tests migration of table transform to merge transformation. Also tests auto alignment conversion to empty string.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Table with Existing Transformations", + "description": "Tests that existing transformations are preserved and new transformation from old format is appended to the list.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "filterFieldsByName", + "spec": { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "field1", + "field2" + ] + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Mixed Threshold Types", + "description": "Tests handling of mixed numeric and string threshold values (int, string, float) with proper type conversion.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "All Color Modes Test", + "description": "Tests all color mode mappings: cell-\u003ecolor-background, row-\u003ecolor-background, value-\u003ecolor-text.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-15" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-16" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-17" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Title", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json new file mode 100644 index 00000000000..3d74b493b8e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json @@ -0,0 +1,1105 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v24.table-angular.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "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.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "All Alignment Options", + "description": "Tests all alignment options: left, center, right, and auto (should convert to empty string).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Field Matcher Types Test", + "description": "Tests both field matcher types: byName for exact matches and byRegexp for regex patterns.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Link Configuration Test", + "description": "Tests various link configurations: with and without tooltip, with and without target blank.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Date Format Variations", + "description": "Tests various date format patterns and aliases.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "React Table - Should NOT Migrate", + "description": "React table (table2) should not be migrated. Properties should remain unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-15": { + "kind": "Panel", + "spec": { + "id": 15, + "title": "Angular Table without Styles - Should NOT Migrate", + "description": "Angular table without styles property should not be migrated.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-16": { + "kind": "Panel", + "spec": { + "id": 16, + "title": "Non-Table Panel - Should NOT Migrate", + "description": "Non-table panels should remain completely unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-17": { + "kind": "Panel", + "spec": { + "id": 17, + "title": "Singlestat Panel - Should NOT Migrate", + "description": "Other panel types should not be affected by table migration.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "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.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Table with Timeseries Aggregations Transform", + "description": "Tests migration of timeseries_aggregations transform to reduce transformation with column mappings (avg-\u003emean, max-\u003emax, min-\u003emin, total-\u003esum, current-\u003elastNotNull, count-\u003ecount).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Table with Timeseries to Rows Transform", + "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Table with Timeseries to Columns Transform", + "description": "Tests migration of timeseries_to_columns transform to seriesToColumns transformation.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Table with Merge Transform", + "description": "Tests migration of table transform to merge transformation. Also tests auto alignment conversion to empty string.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Table with Existing Transformations", + "description": "Tests that existing transformations are preserved and new transformation from old format is appended to the list.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "filterFieldsByName", + "spec": { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "field1", + "field2" + ] + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Mixed Threshold Types", + "description": "Tests handling of mixed numeric and string threshold values (int, string, float) with proper type conversion.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "All Color Modes Test", + "description": "Tests all color mode mappings: cell-\u003ecolor-background, row-\u003ecolor-background, value-\u003ecolor-text.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-15" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-16" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-17" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Title", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v0alpha1.json new file mode 100644 index 00000000000..463a09a3eb4 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v0alpha1.json @@ -0,0 +1,125 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v25.no-op-migration.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "uid": "grafana" + }, + "enable": true, + "name": "Deployments" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "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": "timeseries", + "yAxes": [ + { + "show": true + } + ] + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "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" + ] + }, + "timezone": "", + "title": "V25 No-Op Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json new file mode 100644 index 00000000000..e83721eca1c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json @@ -0,0 +1,221 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v25.no-op-migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Deployments" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with transformations remains unchanged", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Graph", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V25 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "tags should not be removed", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json new file mode 100644 index 00000000000..7266cb6084c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json @@ -0,0 +1,230 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v25.no-op-migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Deployments" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with transformations remains unchanged", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Graph", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V25 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "tags should not be removed", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v0alpha1.json new file mode 100644 index 00000000000..b5af621776b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v0alpha1.json @@ -0,0 +1,117 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v26.text2_to_text.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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": "Text2 Panel", + "type": "text" + }, + { + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "mode": "markdown", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Angular Text Panel", + "type": "text" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "options": { + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "React Text Panel from Angular Panel", + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "No Title", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json new file mode 100644 index 00000000000..001fe0cf137 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json @@ -0,0 +1,244 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v26.text2_to_text.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Text2 Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Angular Text Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "React Text Panel from Angular Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": { + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Title", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json new file mode 100644 index 00000000000..378ae69f16a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json @@ -0,0 +1,251 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v26.text2_to_text.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Text2 Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Angular Text Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "React Text Panel from Angular Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": { + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Title", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v0alpha1.json new file mode 100644 index 00000000000..8c13baae82a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v0alpha1.json @@ -0,0 +1,119 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v27.repeated_panels_and_constant_variable.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Normal Panel", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "panels": [ + { + "autoMigrateFrom": "graph", + "id": 6, + "title": "Normal nested panel", + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row with repeated panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "default_value", + "value": "default_value" + }, + "hide": 0, + "name": "constant_var", + "options": [ + { + "selected": true, + "text": "default_value", + "value": "default_value" + } + ], + "query": "default_value", + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json new file mode 100644 index 00000000000..c722d627187 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json @@ -0,0 +1,211 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v27.repeated_panels_and_constant_variable.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Normal Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Normal nested panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with repeated panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard", + "variables": [ + { + "kind": "TextVariable", + "spec": { + "name": "constant_var", + "current": { + "text": "default_value", + "value": "default_value" + }, + "query": "default_value", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json new file mode 100644 index 00000000000..3fdcd01a27c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json @@ -0,0 +1,215 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v27.repeated_panels_and_constant_variable.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Normal Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Normal nested panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with repeated panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard", + "variables": [ + { + "kind": "TextVariable", + "spec": { + "name": "constant_var", + "current": { + "text": "default_value", + "value": "default_value" + }, + "query": "default_value", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v0alpha1.json new file mode 100644 index 00000000000..6a3a6ef7f8b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v0alpha1.json @@ -0,0 +1,97 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v28.remove_variable_properties.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "query_variable_with_tags", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + }, + { + "name": "custom_variable_with_tags", + "options": [ + { + "text": "Option 1", + "value": "opt1" + }, + { + "text": "Option 2", + "value": "opt2" + } + ], + "type": "custom", + "useTags": false + }, + { + "name": "clean_variable", + "options": [ + { + "text": "Hello", + "value": "World" + } + ], + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json new file mode 100644 index 00000000000..dd56c1cd995 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json @@ -0,0 +1,135 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v28.remove_variable_properties.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "query_variable_with_tags", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable_with_tags", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "Option 1", + "value": "opt1" + }, + { + "selected": false, + "text": "Option 2", + "value": "opt2" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "clean_variable", + "current": { + "text": "", + "value": "" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json new file mode 100644 index 00000000000..c938e4a730f --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json @@ -0,0 +1,138 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v28.remove_variable_properties.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "query_variable_with_tags", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable_with_tags", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "Option 1", + "value": "opt1" + }, + { + "selected": false, + "text": "Option 2", + "value": "opt2" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "clean_variable", + "current": { + "text": "", + "value": "" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v0alpha1.json new file mode 100644 index 00000000000..962c8041848 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v0alpha1.json @@ -0,0 +1,254 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v28.singlestat_and_variable_properties.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 1, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "thresholds": "10,20,30", + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gauge": { + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "thresholds": "10,20,30", + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 3, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "thresholds": "10,20,30", + "type": "stat", + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "query_variable_with_tags", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + }, + { + "name": "custom_variable_with_tags", + "options": [ + { + "text": "Option 1", + "value": "opt1" + }, + { + "text": "Option 2", + "value": "opt2" + } + ], + "type": "custom", + "useTags": false + }, + { + "name": "clean_variable", + "options": [ + { + "text": "Hello", + "value": "World" + } + ], + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json new file mode 100644 index 00000000000..c819be43bc1 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json @@ -0,0 +1,397 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v28.singlestat_and_variable_properties.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "rate(http_requests_total[5m])" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "query_variable_with_tags", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable_with_tags", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "Option 1", + "value": "opt1" + }, + { + "selected": false, + "text": "Option 2", + "value": "opt2" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "clean_variable", + "current": { + "text": "", + "value": "" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json new file mode 100644 index 00000000000..5a0d8b3a96b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json @@ -0,0 +1,410 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v28.singlestat_and_variable_properties.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "rate(http_requests_total[5m])" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "query_variable_with_tags", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable_with_tags", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "Option 1", + "value": "opt1" + }, + { + "selected": false, + "text": "Option 2", + "value": "opt2" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "clean_variable", + "current": { + "text": "", + "value": "" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v0alpha1.json new file mode 100644 index 00000000000..b873e703936 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v0alpha1.json @@ -0,0 +1,442 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v28.singlestat_migration.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 1, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "thresholds": "10,20,30", + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 2, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "thresholds": "", + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gauge": { + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "thresholds": "10,20,30", + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "colors": [ + "#FF0000", + "green", + "orange" + ], + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 10, + "min": 1 + }, + "id": 4, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "thresholds": "10,20,30", + "type": "stat", + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "type": "timeseries" + }, + { + "autoMigrateFrom": "grafana-singlestat-panel", + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": { + "type": "prometheus" + }, + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "id": 5, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "thresholds": "", + "title": "grafana-singlestat-panel", + "type": "stat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "datasource": { + "type": "prometheus" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "id": 6, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "id": 7, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json new file mode 100644 index 00000000000..985b316822d --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json @@ -0,0 +1,626 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v28.singlestat_migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "grafana-singlestat-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "1.0.0", + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "fieldConfig": { + "defaults": { + "unit": "ms", + "mappings": [ + { + "type": "special", + "options": { + "match": "null", + "result": { + "text": "N/A" + } + } + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "1.0.0", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "rate(http_requests_total[5m])" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json new file mode 100644 index 00000000000..2b7d27024c6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json @@ -0,0 +1,646 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v28.singlestat_migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "grafana-singlestat-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "PD8C576611E62080A" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "PD8C576611E62080A" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "1.0.0", + "spec": { + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "fieldConfig": { + "defaults": { + "unit": "ms", + "mappings": [ + { + "type": "special", + "options": { + "match": "null", + "result": { + "text": "N/A" + } + } + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "PD8C576611E62080A" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "1.0.0", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "rate(http_requests_total[5m])" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v0alpha1.json new file mode 100644 index 00000000000..9b05758f191 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v0alpha1.json @@ -0,0 +1,187 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v29.query_variables_refresh_and_options.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "uid": "prometheus" + }, + "id": 1, + "targets": [ + { + "datasource": { + "uid": "prometheus" + }, + "expr": "up", + "refId": "A" + } + ], + "title": "Test Panel", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "never_refresh_with_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "never_refresh_without_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "dashboard_refresh_with_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "dashboard_refresh_without_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "timerange_refresh_with_options", + "options": [], + "refresh": 2, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "timerange_refresh_without_options", + "options": [], + "refresh": 2, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "no_refresh_with_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "no_refresh_without_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "unknown_refresh_with_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "datasource": "prometheus", + "name": "unknown_refresh_without_options", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "name": "custom_variable", + "options": [ + { + "text": "custom", + "value": "custom" + } + ], + "type": "custom" + }, + { + "name": "textbox_variable", + "options": [ + { + "text": "Hello", + "value": "World" + } + ], + "type": "textbox" + }, + { + "name": "datasource_variable", + "options": [], + "type": "datasource" + }, + { + "name": "interval_variable", + "options": [ + { + "text": "1m", + "value": "1m" + } + ], + "type": "interval" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V29 Query Variables Refresh and Options Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json new file mode 100644 index 00000000000..ce4f5103c7d --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json @@ -0,0 +1,439 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v29.query_variables_refresh_and_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Test Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "up" + } + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V29 Query Variables Refresh and Options Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "never_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "never_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "dashboard_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "dashboard_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "timerange_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "timerange_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "unknown_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "unknown_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "custom", + "value": "custom" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "textbox_variable", + "current": { + "text": "", + "value": "" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource_variable", + "pluginId": "prometheus", + "refresh": "never", + "regex": "", + "current": { + "text": "", + "value": "" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "IntervalVariable", + "spec": { + "name": "interval_variable", + "query": "1m", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + } + ], + "auto": false, + "auto_min": "", + "auto_count": 0, + "refresh": "onTimeRangeChanged", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json new file mode 100644 index 00000000000..98d2361f0ae --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json @@ -0,0 +1,462 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v29.query_variables_refresh_and_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Test Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "prometheus" + }, + "spec": { + "expr": "up" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V29 Query Variables Refresh and Options Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "never_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "never_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "dashboard_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "dashboard_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "timerange_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "timerange_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "no_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "unknown_refresh_with_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "unknown_refresh_without_options", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_variable", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "custom", + "value": "custom" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "textbox_variable", + "current": { + "text": "", + "value": "" + }, + "query": "", + "hide": "dontHide", + "skipUrlSync": false + } + }, + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource_variable", + "pluginId": "prometheus", + "refresh": "never", + "regex": "", + "current": { + "text": "", + "value": "" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "IntervalVariable", + "spec": { + "name": "interval_variable", + "query": "1m", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + } + ], + "auto": false, + "auto_min": "", + "auto_count": 0, + "refresh": "onTimeRangeChanged", + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v0alpha1.json new file mode 100644 index 00000000000..c18be5720dd --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v0alpha1.json @@ -0,0 +1,127 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v3.no-op.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "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" + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "barchart" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json new file mode 100644 index 00000000000..4916dcaa820 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json @@ -0,0 +1,298 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v3.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json new file mode 100644 index 00000000000..4a38725abc3 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json @@ -0,0 +1,307 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v3.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v0alpha1.json new file mode 100644 index 00000000000..4c3176f47e9 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v0alpha1.json @@ -0,0 +1,400 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v30.value_mappings_and_tooltip_options.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "0": { + "text": "Down" + }, + "1": { + "text": "Up" + } + }, + "type": "value" + }, + { + "options": { + "from": 10, + "result": { + "text": "Medium" + }, + "to": 20 + }, + "type": "range" + }, + { + "options": { + "match": "null", + "result": { + "text": "Null Value" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "test-field" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "1": { + "text": "Override Up" + } + }, + "type": "value" + } + ] + } + ] + } + ] + }, + "id": 1, + "options": { + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with legacy value mappings and tooltip options", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "options": { + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "XY Chart with tooltip options only", + "type": "xychart" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "options": { + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "XY Chart2 with tooltip options", + "type": "xychart2" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "options": { + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph panel gets migrated to timeseries and tooltip", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "100": { + "text": "Critical" + } + }, + "type": "value" + }, + { + "options": { + "from": 50, + "result": { + "text": "Warning" + }, + "to": 99 + }, + "type": "range" + }, + { + "options": { + "from": 0, + "result": { + "text": "OK" + }, + "to": 49 + }, + "type": "range" + } + ] + }, + "overrides": [] + }, + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with complex value mappings", + "type": "stat" + }, + { + "collapsed": true, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 6, + "panels": [ + { + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "0": { + "text": "Off" + }, + "1": { + "text": "On" + } + }, + "type": "value" + } + ] + } + }, + "id": 7, + "options": { + "tooltip": { + "mode": "multi" + } + }, + "title": "Nested panel with both migrations", + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row with nested panels", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + }, + "id": 8, + "options": { + "legend": { + "displayMode": "list", + "showLegend": true + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no relevant configurations", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "empty-field" + }, + "properties": [ + { + "id": "mappings", + "value": [] + } + ] + } + ] + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with empty mappings array - should return null", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json new file mode 100644 index 00000000000..5d81e9bab64 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json @@ -0,0 +1,693 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v30.value_mappings_and_tooltip_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with legacy value mappings and tooltip options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "tooltip": { + "mode": "multi" + } + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "0": { + "text": "Down" + }, + "1": { + "text": "Up" + } + } + }, + { + "type": "range", + "options": { + "from": 10, + "to": 20, + "result": { + "text": "Medium" + } + } + }, + { + "type": "special", + "options": { + "match": "null", + "result": { + "text": "Null Value" + } + } + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "test-field" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "1": { + "text": "Override Up" + } + }, + "type": "value" + } + ] + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "XY Chart with tooltip options only", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "xychart", + "spec": { + "pluginVersion": "", + "options": { + "tooltip": { + "mode": "single" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "XY Chart2 with tooltip options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "xychart2", + "spec": { + "pluginVersion": "", + "options": { + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Graph panel gets migrated to timeseries and tooltip", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "tooltip": { + "mode": "single" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with complex value mappings", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "100": { + "text": "Critical" + } + } + }, + { + "type": "range", + "options": { + "from": 50, + "to": 99, + "result": { + "text": "Warning" + } + } + }, + { + "type": "range", + "options": { + "from": 0, + "to": 49, + "result": { + "text": "OK" + } + } + } + ] + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Nested panel with both migrations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "tooltip": { + "mode": "multi" + } + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "0": { + "text": "Off" + }, + "1": { + "text": "On" + } + } + } + ] + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with no relevant configurations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "list", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with empty mappings array - should return null", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "empty-field" + }, + "properties": [ + { + "id": "mappings", + "value": [] + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row with nested panels", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json new file mode 100644 index 00000000000..6848d2d1714 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json @@ -0,0 +1,709 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v30.value_mappings_and_tooltip_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with legacy value mappings and tooltip options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "tooltip": { + "mode": "multi" + } + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "0": { + "text": "Down" + }, + "1": { + "text": "Up" + } + } + }, + { + "type": "range", + "options": { + "from": 10, + "to": 20, + "result": { + "text": "Medium" + } + } + }, + { + "type": "special", + "options": { + "match": "null", + "result": { + "text": "Null Value" + } + } + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "test-field" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "1": { + "text": "Override Up" + } + }, + "type": "value" + } + ] + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "XY Chart with tooltip options only", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "xychart", + "version": "", + "spec": { + "options": { + "tooltip": { + "mode": "single" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "XY Chart2 with tooltip options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "xychart2", + "version": "", + "spec": { + "options": { + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Graph panel gets migrated to timeseries and tooltip", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "tooltip": { + "mode": "single" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with complex value mappings", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "100": { + "text": "Critical" + } + } + }, + { + "type": "range", + "options": { + "from": 50, + "to": 99, + "result": { + "text": "Warning" + } + } + }, + { + "type": "range", + "options": { + "from": 0, + "to": 49, + "result": { + "text": "OK" + } + } + } + ] + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Nested panel with both migrations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "tooltip": { + "mode": "multi" + } + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "type": "value", + "options": { + "0": { + "text": "Off" + }, + "1": { + "text": "On" + } + } + } + ] + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with no relevant configurations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "list", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with empty mappings array - should return null", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "empty-field" + }, + "properties": [ + { + "id": "mappings", + "value": [] + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row with nested panels", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v0alpha1.json new file mode 100644 index 00000000000..9cbd5912975 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v0alpha1.json @@ -0,0 +1,304 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v31.labels_to_fields_merge.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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 basic labelsToFields transformation", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with labelsToFields options preserved", + "transformations": [ + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance", + "region" + ], + "mode": "rows", + "valueLabel": "value" + } + }, + { + "id": "merge", + "options": {} + } + ], + "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": "Panel with multiple labelsToFields transformations", + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "calculateField", + "options": {} + }, + { + "id": "labelsToFields", + "options": { + "mode": "rows" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no transformations", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with other transformations only", + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ], + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 5, + "panels": [ + { + "id": 6, + "title": "Nested panel with labelsToFields", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "id": 7, + "title": "Nested panel without labelsToFields", + "transformations": [ + { + "id": "organize", + "options": {} + } + ], + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row with nested panels", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with labelsToFields and existing merge", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V31 LabelsToFields Merge Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json new file mode 100644 index 00000000000..1ad2c6f1b29 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json @@ -0,0 +1,673 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v31.labels_to_fields_merge.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with basic labelsToFields transformation", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with multiple labelsToFields transformations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "organize", + "spec": { + "id": "organize", + "options": {} + } + }, + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + }, + { + "kind": "calculateField", + "spec": { + "id": "calculateField", + "options": {} + } + }, + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "mode": "rows" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with no transformations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with other transformations only", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "organize", + "spec": { + "id": "organize", + "options": {} + } + }, + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Nested panel with labelsToFields", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Nested panel without labelsToFields", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "organize", + "spec": { + "id": "organize", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with labelsToFields and existing merge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + }, + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with labelsToFields options preserved", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance", + "region" + ], + "mode": "rows", + "valueLabel": "value" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with nested panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V31 LabelsToFields Merge Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json new file mode 100644 index 00000000000..d7f0684597c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json @@ -0,0 +1,688 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v31.labels_to_fields_merge.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with basic labelsToFields transformation", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with multiple labelsToFields transformations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "organize", + "spec": { + "id": "organize", + "options": {} + } + }, + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + }, + { + "kind": "calculateField", + "spec": { + "id": "calculateField", + "options": {} + } + }, + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "mode": "rows" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with no transformations", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with other transformations only", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "organize", + "spec": { + "id": "organize", + "options": {} + } + }, + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Nested panel with labelsToFields", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Nested panel without labelsToFields", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "organize", + "spec": { + "id": "organize", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with labelsToFields and existing merge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + }, + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with labelsToFields options preserved", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance", + "region" + ], + "mode": "rows", + "valueLabel": "value" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with nested panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V31 LabelsToFields Merge Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v0alpha1.json new file mode 100644 index 00000000000..7d612b2ed67 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v0alpha1.json @@ -0,0 +1,173 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v32.no_op_migration.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "uid": "grafana" + }, + "enable": true, + "name": "Deployments" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "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": [ + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance" + ], + "mode": "rows" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "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 panel remains unchanged", + "type": "timeseries", + "yAxes": [ + { + "show": true + } + ] + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "panels": [ + { + "fieldConfig": { + "defaults": { + "unit": "bytes" + } + }, + "id": 4, + "title": "Nested stat panel", + "type": "stat" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row with nested panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "environment", + "options": [], + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V32 No-Op Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json new file mode 100644 index 00000000000..1d68e967a49 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json @@ -0,0 +1,314 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v32.no_op_migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Deployments" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with transformations remains unchanged", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance" + ], + "mode": "rows" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Graph panel remains unchanged", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Nested stat panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with nested panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V32 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "environment", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json new file mode 100644 index 00000000000..b80ef22e794 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json @@ -0,0 +1,324 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v32.no_op_migration.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Deployments" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with transformations remains unchanged", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance" + ], + "mode": "rows" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Graph panel remains unchanged", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Nested stat panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with nested panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V32 No-Op Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "environment", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v0alpha1.json new file mode 100644 index 00000000000..a178fe73218 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v0alpha1.json @@ -0,0 +1,307 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v33.panel_ds_name_to_ref.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Tests v33 migration behavior when panel datasource is explicitly null. Should remain null after migration (returnDefaultAsNull: true).", + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Target with UID reference should migrate to full object", + "refId": "A" + } + ], + "title": "Panel Datasource: null → should stay null", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "description": "Tests v33 migration behavior when panel datasource is already a proper object reference. Should remain unchanged.", + "id": 2, + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Target with existing object should remain unchanged", + "refId": "A" + } + ], + "title": "Panel Datasource: existing object → should stay unchanged", + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Tests v33 migration when panel datasource is a string name. Should convert to proper object with uid, type, apiVersion.", + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel Datasource: string name → should migrate to object", + "type": "table" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests v33 migration when panel has datasource string but empty targets array. Panel datasource should still migrate.", + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel Datasource: string name with empty targets → should migrate", + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Tests v33 target migration with various edge cases: null target (unchanged), valid string (migrated), non-existing string (preserved), missing datasource field (unchanged).", + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Null target datasource should remain null", + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Valid string should migrate to object", + "refId": "B" + }, + { + "datasource": { + "uid": "non-existing-ds" + }, + "description": "Non-existing datasource should be preserved as-is (migration returns nil)", + "refId": "C" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Target without datasource field should remain unchanged", + "refId": "D" + } + ], + "title": "Target Datasources: mixed null/string/non-existing scenarios", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests v33 migration when panel datasource is null but targets have mixed reference types (object, string). Panel should stay null, targets should migrate appropriately.", + "id": 6, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref" + }, + "description": "Existing object target should remain unchanged", + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "String target should migrate to object", + "refId": "B" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Default datasource string should migrate to object", + "refId": "C" + } + ], + "title": "Panel: null datasource with mixed target types", + "type": "timeseries" + }, + { + "datasource": {}, + "description": "Tests v33 migration behavior with empty string datasource. Should migrate to empty object {} based on MigrateDatasourceNameToRef logic.", + "id": 7, + "targets": [ + { + "datasource": {}, + "description": "Empty string target should also migrate to empty object {}", + "refId": "A" + } + ], + "title": "Empty string datasource → should return empty object {}", + "type": "stat" + }, + { + "datasource": { + "uid": "completely-missing-ds" + }, + "description": "Tests v33 migration with completely unknown datasource names. Since migration returns nil for unknown datasources, they should be preserved unchanged.", + "id": 8, + "targets": [ + { + "datasource": { + "uid": "also-missing-ds" + }, + "description": "Unknown target datasource should remain unchanged (migration returns nil)", + "refId": "A" + }, + { + "datasource": { + "uid": "completely-missing-ds" + }, + "description": "Empty string target should migrate to {}", + "refId": "B" + } + ], + "title": "Non-existing datasources → should be preserved as-is", + "type": "table" + }, + { + "collapsed": true, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests v33 migration handles nested panels within collapsed rows. Nested panel datasources should migrate same as top-level panels.", + "id": 9, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Nested panel with string datasource should migrate to proper object reference, proving row panel recursion works.", + "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Nested target should also migrate from string to object", + "refId": "A" + } + ], + "title": "Nested Panel: string datasource → should migrate to object", + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row Panel: nested panels should also migrate", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V33 Panel Datasource Name to Ref Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json new file mode 100644 index 00000000000..c38887d458c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json @@ -0,0 +1,724 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v33.panel_ds_name_to_ref.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel Datasource: null → should stay null", + "description": "Tests v33 migration behavior when panel datasource is explicitly null. Should remain null after migration (returnDefaultAsNull: true).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Target with UID reference should migrate to full object" + } + }, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Nested Panel: string datasource → should migrate to object", + "description": "Nested panel with string datasource should migrate to proper object reference, proving row panel recursion works.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Nested target should also migrate from string to object" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel Datasource: existing object → should stay unchanged", + "description": "Tests v33 migration behavior when panel datasource is already a proper object reference. Should remain unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Target with existing object should remain unchanged" + } + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel Datasource: string name → should migrate to object", + "description": "Tests v33 migration when panel datasource is a string name. Should convert to proper object with uid, type, apiVersion.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel Datasource: string name with empty targets → should migrate", + "description": "Tests v33 migration when panel has datasource string but empty targets array. Panel datasource should still migrate.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Target Datasources: mixed null/string/non-existing scenarios", + "description": "Tests v33 target migration with various edge cases: null target (unchanged), valid string (migrated), non-existing string (preserved), missing datasource field (unchanged).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Null target datasource should remain null" + } + }, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Valid string should migrate to object" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Non-existing datasource should be preserved as-is (migration returns nil)" + } + }, + "datasource": { + "type": "prometheus", + "uid": "non-existing-ds" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Target without datasource field should remain unchanged" + } + }, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel: null datasource with mixed target types", + "description": "Tests v33 migration when panel datasource is null but targets have mixed reference types (object, string). Panel should stay null, targets should migrate appropriately.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Existing object target should remain unchanged" + } + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "String target should migrate to object" + } + }, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Default datasource string should migrate to object" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Empty string datasource → should return empty object {}", + "description": "Tests v33 migration behavior with empty string datasource. Should migrate to empty object {} based on MigrateDatasourceNameToRef logic.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Empty string target should also migrate to empty object {}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Non-existing datasources → should be preserved as-is", + "description": "Tests v33 migration with completely unknown datasource names. Since migration returns nil for unknown datasources, they should be preserved unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Unknown target datasource should remain unchanged (migration returns nil)" + } + }, + "datasource": { + "type": "prometheus", + "uid": "also-missing-ds" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "description": "Empty string target should migrate to {}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "completely-missing-ds" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row Panel: nested panels should also migrate", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V33 Panel Datasource Name to Ref Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json new file mode 100644 index 00000000000..fbb8d83b728 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json @@ -0,0 +1,750 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v33.panel_ds_name_to_ref.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel Datasource: null → should stay null", + "description": "Tests v33 migration behavior when panel datasource is explicitly null. Should remain null after migration (returnDefaultAsNull: true).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": { + "description": "Target with UID reference should migrate to full object" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Nested Panel: string datasource → should migrate to object", + "description": "Nested panel with string datasource should migrate to proper object reference, proving row panel recursion works.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "description": "Nested target should also migrate from string to object" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel Datasource: existing object → should stay unchanged", + "description": "Tests v33 migration behavior when panel datasource is already a proper object reference. Should remain unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": { + "description": "Target with existing object should remain unchanged" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel Datasource: string name → should migrate to object", + "description": "Tests v33 migration when panel datasource is a string name. Should convert to proper object with uid, type, apiVersion.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel Datasource: string name with empty targets → should migrate", + "description": "Tests v33 migration when panel has datasource string but empty targets array. Panel datasource should still migrate.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Target Datasources: mixed null/string/non-existing scenarios", + "description": "Tests v33 target migration with various edge cases: null target (unchanged), valid string (migrated), non-existing string (preserved), missing datasource field (unchanged).", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": { + "description": "Null target datasource should remain null" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "description": "Valid string should migrate to object" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "non-existing-ds" + }, + "spec": { + "description": "Non-existing datasource should be preserved as-is (migration returns nil)" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": { + "description": "Target without datasource field should remain unchanged" + } + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel: null datasource with mixed target types", + "description": "Tests v33 migration when panel datasource is null but targets have mixed reference types (object, string). Panel should stay null, targets should migrate appropriately.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref" + }, + "spec": { + "description": "Existing object target should remain unchanged" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": { + "description": "String target should migrate to object" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "description": "Default datasource string should migrate to object" + } + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Empty string datasource → should return empty object {}", + "description": "Tests v33 migration behavior with empty string datasource. Should migrate to empty object {} based on MigrateDatasourceNameToRef logic.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "description": "Empty string target should also migrate to empty object {}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Non-existing datasources → should be preserved as-is", + "description": "Tests v33 migration with completely unknown datasource names. Since migration returns nil for unknown datasources, they should be preserved unchanged.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "also-missing-ds" + }, + "spec": { + "description": "Unknown target datasource should remain unchanged (migration returns nil)" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "completely-missing-ds" + }, + "spec": { + "description": "Empty string target should migrate to {}" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row Panel: nested panels should also migrate", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V33 Panel Datasource Name to Ref Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v0alpha1.json new file mode 100644 index 00000000000..a8caa269744 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v0alpha1.json @@ -0,0 +1,1045 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v34.multiple_stats_cloudwatch.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "enable": true, + "iconColor": "red", + "name": "CloudWatch Annotation Single Statistic", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-789012" + }, + "enable": true, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Maximum", + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Maximum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "LoadBalancer": "my-lb" + }, + "enable": true, + "iconColor": "green", + "name": "CloudWatch Annotation Empty Statistics", + "namespace": "AWS/ApplicationELB", + "prefixMatching": false, + "region": "us-west-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - InvalidStat", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "InvalidStat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - null", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - 123", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": 123 + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": true, + "iconColor": "purple", + "name": "Non-CloudWatch Annotation" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-789012" + }, + "enable": true, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Minimum", + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Minimum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-789012" + }, + "enable": true, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Sum", + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Sum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Sum", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Sum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - null", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Average", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - Average", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - ", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - true", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": {} + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "refId": "C", + "region": "us-east-1", + "statistic": "Minimum" + } + ], + "title": "CloudWatch Single Query Multiple Statistics", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "LoadBalancer": "my-load-balancer" + }, + "metricEditorMode": 0, + "metricName": "RequestCount", + "metricQueryType": 0, + "namespace": "AWS/ApplicationELB", + "refId": "A", + "region": "us-west-2", + "statistic": "Sum" + } + ], + "title": "CloudWatch Single Query Single Statistic", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "DBInstanceIdentifier": "my-db" + }, + "metricEditorMode": 0, + "metricName": "DatabaseConnections", + "metricQueryType": 0, + "namespace": "AWS/RDS", + "refId": "A", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query No Statistics Array", + "type": "timeseries" + }, + { + "datasource": { + "uid": "prometheus" + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "uid": "prometheus" + }, + "expr": "up", + "refId": "B" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TopicName": "my-topic" + }, + "metricEditorMode": 0, + "metricName": "NumberOfMessagesPublished", + "metricQueryType": 0, + "namespace": "AWS/SNS", + "refId": "C", + "region": "us-west-1", + "statistic": "Sum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "refId": "D", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "Mixed CloudWatch and Non-CloudWatch Queries", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "BucketName": "my-bucket" + }, + "metricEditorMode": 0, + "metricName": "BucketSizeBytes", + "metricQueryType": 0, + "namespace": "AWS/S3", + "refId": "A", + "region": "us-east-1" + } + ], + "title": "CloudWatch Query Empty Statistics", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "A", + "region": "us-west-2", + "statistic": "InvalidStat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "B", + "region": "us-west-2", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "C", + "region": "us-west-2" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "D", + "region": "us-west-2", + "statistic": "Maximum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "E", + "region": "us-west-2", + "statistic": "" + } + ], + "title": "CloudWatch Query Invalid Statistics", + "type": "timeseries" + }, + { + "collapsed": true, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 7, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "refId": "A", + "region": "us-east-1", + "statistic": "Sum" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "refId": "B", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "refId": "C", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "Nested CloudWatch Query Multiple Statistics", + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row with CloudWatch", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Existing Editor Mode", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Query Missing Editor Fields", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 11, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Expression (Code Mode)", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 12, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-insights" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Insights Query Missing Editor Mode", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 13, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Null Statistics", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 14, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": 123 + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": {} + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": [] + } + ], + "title": "CloudWatch Query with Only Invalid Statistics", + "type": "timeseries" + }, + { + "datasource": { + "uid": "prometheus" + }, + "id": 15, + "targets": [ + { + "datasource": { + "uid": "prometheus" + }, + "expr": "cpu_usage", + "refId": "A" + } + ], + "title": "Non-CloudWatch Panel", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "CloudWatch Multiple Statistics Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json new file mode 100644 index 00000000000..963c1e4b649 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json @@ -0,0 +1,1904 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v34.multiple_stats_cloudwatch.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "CloudWatch Annotation Single Statistic", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-123456" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Maximum", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-789012" + }, + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Maximum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "green", + "name": "CloudWatch Annotation Empty Statistics", + "legacyOptions": { + "dimensions": { + "LoadBalancer": "my-lb" + }, + "namespace": "AWS/ApplicationELB", + "prefixMatching": false, + "region": "us-west-1" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - InvalidStat", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "InvalidStat" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - null", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - 123", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": 123 + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "Non-CloudWatch Annotation" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Minimum", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-789012" + }, + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Minimum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Sum", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-789012" + }, + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Sum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Sum", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Sum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - null", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Average", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - Average", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - ", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - true", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": true + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": {} + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CloudWatch Single Query Multiple Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "region": "us-east-1", + "statistic": "Minimum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "CloudWatch Query Missing Editor Fields", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "CloudWatch Query with Expression (Code Mode)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "CloudWatch Insights Query Missing Editor Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-insights" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "CloudWatch Query with Null Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "CloudWatch Query with Only Invalid Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": 123 + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": true + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": {} + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": [] + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-15": { + "kind": "Panel", + "spec": { + "id": 15, + "title": "Non-CloudWatch Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "cpu_usage" + } + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "CloudWatch Single Query Single Statistic", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "LoadBalancer": "my-load-balancer" + }, + "metricEditorMode": 0, + "metricName": "RequestCount", + "metricQueryType": 0, + "namespace": "AWS/ApplicationELB", + "region": "us-west-2", + "statistic": "Sum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "CloudWatch Query No Statistics Array", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "DBInstanceIdentifier": "my-db" + }, + "metricEditorMode": 0, + "metricName": "DatabaseConnections", + "metricQueryType": 0, + "namespace": "AWS/RDS", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Mixed CloudWatch and Non-CloudWatch Queries", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "up" + } + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "TopicName": "my-topic" + }, + "metricEditorMode": 0, + "metricName": "NumberOfMessagesPublished", + "metricQueryType": 0, + "namespace": "AWS/SNS", + "region": "us-west-1", + "statistic": "Sum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "CloudWatch Query Empty Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "BucketName": "my-bucket" + }, + "metricEditorMode": 0, + "metricName": "BucketSizeBytes", + "metricQueryType": 0, + "namespace": "AWS/S3", + "region": "us-east-1" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "CloudWatch Query Invalid Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "InvalidStat" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Nested CloudWatch Query Multiple Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "region": "us-east-1", + "statistic": "Sum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "CloudWatch Query with Existing Editor Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "region": "us-east-1", + "statistic": "Average" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row with CloudWatch", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-15" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "CloudWatch Multiple Statistics Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json new file mode 100644 index 00000000000..79ee3db369e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json @@ -0,0 +1,1968 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v34.multiple_stats_cloudwatch.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "CloudWatch Annotation Single Statistic", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-123456" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Maximum", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-789012" + }, + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Maximum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "green", + "name": "CloudWatch Annotation Empty Statistics", + "legacyOptions": { + "dimensions": { + "LoadBalancer": "my-lb" + }, + "namespace": "AWS/ApplicationELB", + "prefixMatching": false, + "region": "us-west-1" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - InvalidStat", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "InvalidStat" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - null", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - 123", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": 123 + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "Non-CloudWatch Annotation" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Minimum", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-789012" + }, + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Minimum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Sum", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-789012" + }, + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Sum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Sum", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Sum" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - null", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Average", + "legacyOptions": { + "dimensions": { + "TableName": "my-table" + }, + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - Average", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - ", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - true", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": true + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", + "legacyOptions": { + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": {} + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CloudWatch Single Query Multiple Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "region": "us-east-1", + "statistic": "Minimum" + } + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "CloudWatch Query Missing Editor Fields", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "CloudWatch Query with Expression (Code Mode)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "CloudWatch Insights Query Missing Editor Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-insights" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "CloudWatch Query with Null Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "CloudWatch Query with Only Invalid Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": 123 + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": true + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": {} + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "region": "us-east-1", + "statistic": [] + } + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-15": { + "kind": "Panel", + "spec": { + "id": 15, + "title": "Non-CloudWatch Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "prometheus" + }, + "spec": { + "expr": "cpu_usage" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "CloudWatch Single Query Single Statistic", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "LoadBalancer": "my-load-balancer" + }, + "metricEditorMode": 0, + "metricName": "RequestCount", + "metricQueryType": 0, + "namespace": "AWS/ApplicationELB", + "region": "us-west-2", + "statistic": "Sum" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "CloudWatch Query No Statistics Array", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "DBInstanceIdentifier": "my-db" + }, + "metricEditorMode": 0, + "metricName": "DatabaseConnections", + "metricQueryType": 0, + "namespace": "AWS/RDS", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Mixed CloudWatch and Non-CloudWatch Queries", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "prometheus" + }, + "spec": { + "expr": "up" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "TopicName": "my-topic" + }, + "metricEditorMode": 0, + "metricName": "NumberOfMessagesPublished", + "metricQueryType": 0, + "namespace": "AWS/SNS", + "region": "us-west-1", + "statistic": "Sum" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "D", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "CloudWatch Query Empty Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "BucketName": "my-bucket" + }, + "metricEditorMode": 0, + "metricName": "BucketSizeBytes", + "metricQueryType": 0, + "namespace": "AWS/S3", + "region": "us-east-1" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "CloudWatch Query Invalid Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "InvalidStat" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "Average" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "Maximum" + } + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "region": "us-west-2", + "statistic": "" + } + }, + "refId": "E", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Nested CloudWatch Query Multiple Statistics", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "region": "us-east-1", + "statistic": "Sum" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "C", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "CloudWatch Query with Existing Editor Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "region": "us-east-1", + "statistic": "Average" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "region": "us-east-1", + "statistic": "Maximum" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row with CloudWatch", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-15" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "CloudWatch Multiple Statistics Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v0alpha1.json new file mode 100644 index 00000000000..747581ee194 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v0alpha1.json @@ -0,0 +1,308 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v35.ensure_x_axis_visibility.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + }, + "id": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Hidden Axis", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Series A" + }, + "properties": [ + { + "id": "color.mode", + "value": "palette-classic" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + }, + "id": 7, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Hidden Axis and Existing Overrides", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "auto" + } + }, + "overrides": [] + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Auto Axis (No Change Expected)", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [] + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Stat Panel with Hidden Axis (No Change Expected)", + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing FieldConfig", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing Defaults", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + }, + "id": 11, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing Custom Config", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + }, + "id": 12, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing Overrides Array", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "X-Axis Visibility Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json new file mode 100644 index 00000000000..936aeb8c30f --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json @@ -0,0 +1,599 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v35.ensure_x_axis_visibility.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Timeseries with Missing Defaults", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Timeseries with Missing Custom Config", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Timeseries with Missing Overrides Array", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Timeseries with Missing FieldConfig", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Timeseries with Hidden Axis", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Timeseries with Hidden Axis and Existing Overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Series A" + }, + "properties": [ + { + "id": "color.mode", + "value": "palette-classic" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Timeseries with Auto Axis (No Change Expected)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "auto" + } + }, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Stat Panel with Hidden Axis (No Change Expected)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "X-Axis Visibility Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json new file mode 100644 index 00000000000..f9be2a6cee1 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json @@ -0,0 +1,616 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v35.ensure_x_axis_visibility.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Timeseries with Missing Defaults", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Timeseries with Missing Custom Config", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "unit": "bytes" + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Timeseries with Missing Overrides Array", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Timeseries with Missing FieldConfig", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Timeseries with Hidden Axis", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Timeseries with Hidden Axis and Existing Overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Series A" + }, + "properties": [ + { + "id": "color.mode", + "value": "palette-classic" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Timeseries with Auto Axis (No Change Expected)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "auto" + } + }, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Stat Panel with Hidden Axis (No Change Expected)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "X-Axis Visibility Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v0alpha1.json new file mode 100644 index 00000000000..a5dd8e99a9b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v0alpha1.json @@ -0,0 +1,427 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v36.ds_name_to_ref.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "name": "Default Annotation - Tests default datasource migration" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "name": "Named Datasource Annotation - Tests migration by datasource name" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "name": "UID Datasource Annotation - Tests migration by datasource UID" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "name": "Null Datasource Annotation - Tests null datasource fallback to default" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "unknown-datasource-name" + }, + "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests null panel datasource migration with targets - should fallback to default", + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Null Datasource and Targets" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests null panel datasource with empty targets array - should create default target", + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Null Datasource and Empty Targets" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests null panel datasource with missing targets - should create default target array", + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with No Targets Array" + }, + { + "datasource": { + "uid": "-- Mixed --" + }, + "description": "Tests mixed datasource panel - targets should migrate independently", + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "B" + } + ], + "title": "Panel with Mixed Datasources" + }, + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "description": "Tests panel with already migrated datasource object - should preserve existing refs", + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Existing Object Datasource" + }, + { + "datasource": { + "uid": "unknown-panel-datasource" + }, + "description": "Tests panel with unknown datasource - should preserve as UID-only reference", + "id": 6, + "targets": [ + { + "datasource": { + "uid": "unknown-target-datasource" + }, + "refId": "A" + } + ], + "title": "Panel with Unknown Datasource Name" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Tests panel with expression query - should not inherit expression as panel datasource", + "id": 7, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "refId": "B" + } + ], + "title": "Panel with Expression Query" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Tests panel inheriting datasource from target when panel datasource was default", + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel Inheriting from Target" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Tests panel with datasource referenced by name - should migrate to full object", + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Named Datasource" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Tests panel with datasource referenced by UID - should migrate to full object", + "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel with UID Datasource" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests row panel - it gets datasource or targets fields added even it is not needed, but this is how it works in frontend", + "id": 11, + "panels": [], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Simple Row Panel", + "type": "row" + }, + { + "collapsed": true, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Tests collapsed row with nested panels - nested panels should migrate", + "id": 12, + "panels": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Nested panel in collapsed row with default datasource", + "id": 13, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Nested Panel with Default Datasource" + }, + { + "datasource": { + "uid": "unknown-nested-datasource" + }, + "description": "Nested panel in collapsed row with unknown datasource", + "id": 14, + "targets": [ + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Nested Panel with Unknown Datasource" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row with Nested Panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "name": "query_var_null", + "options": [], + "type": "query" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "name": "query_var_named", + "options": [], + "type": "query" + }, + { + "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "name": "query_var_uid", + "options": [], + "type": "query" + }, + { + "datasource": { + "uid": "unknown-datasource" + }, + "name": "query_var_unknown", + "options": [], + "type": "query" + }, + { + "name": "non_query_var", + "type": "constant" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Datasource Reference Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json new file mode 100644 index 00000000000..17a351eaa46 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json @@ -0,0 +1,1033 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v36.ds_name_to_ref.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Default Annotation - Tests default datasource migration" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Named Datasource Annotation - Tests migration by datasource name" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "UID Datasource Annotation - Tests migration by datasource UID" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Null Datasource Annotation - Tests null datasource fallback to default" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "unknown-datasource-name" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with Null Datasource and Targets", + "description": "Tests null panel datasource migration with targets - should fallback to default", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Panel with UID Datasource", + "description": "Tests panel with datasource referenced by UID - should migrate to full object", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Nested Panel with Default Datasource", + "description": "Nested panel in collapsed row with default datasource", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "Nested Panel with Unknown Datasource", + "description": "Nested panel in collapsed row with unknown datasource", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with Null Datasource and Empty Targets", + "description": "Tests null panel datasource with empty targets array - should create default target", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with No Targets Array", + "description": "Tests null panel datasource with missing targets - should create default target array", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with Mixed Datasources", + "description": "Tests mixed datasource panel - targets should migrate independently", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with Existing Object Datasource", + "description": "Tests panel with already migrated datasource object - should preserve existing refs", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with Unknown Datasource Name", + "description": "Tests panel with unknown datasource - should preserve as UID-only reference", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "unknown-target-datasource" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with Expression Query", + "description": "Tests panel with expression query - should not inherit expression as panel datasource", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel Inheriting from Target", + "description": "Tests panel inheriting datasource from target when panel datasource was default", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with Named Datasource", + "description": "Tests panel with datasource referenced by name - should migrate to full object", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Simple Row Panel", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row with Nested Panels", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Datasource Reference Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_null", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_named", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_uid", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_unknown", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "datasource": { + "type": "prometheus", + "uid": "unknown-datasource" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "ConstantVariable", + "spec": { + "name": "non_query_var", + "query": "", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json new file mode 100644 index 00000000000..6465fae7ed0 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json @@ -0,0 +1,1069 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v36.ds_name_to_ref.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Default Annotation - Tests default datasource migration" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Named Datasource Annotation - Tests migration by datasource name" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "UID Datasource Annotation - Tests migration by datasource UID" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Null Datasource Annotation - Tests null datasource fallback to default" + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "unknown-datasource-name" + }, + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with Null Datasource and Targets", + "description": "Tests null panel datasource migration with targets - should fallback to default", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Panel with UID Datasource", + "description": "Tests panel with datasource referenced by UID - should migrate to full object", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Nested Panel with Default Datasource", + "description": "Nested panel in collapsed row with default datasource", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "id": 14, + "title": "Nested Panel with Unknown Datasource", + "description": "Nested panel in collapsed row with unknown datasource", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with Null Datasource and Empty Targets", + "description": "Tests null panel datasource with empty targets array - should create default target", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with No Targets Array", + "description": "Tests null panel datasource with missing targets - should create default target array", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with Mixed Datasources", + "description": "Tests mixed datasource panel - targets should migrate independently", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with Existing Object Datasource", + "description": "Tests panel with already migrated datasource object - should preserve existing refs", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with Unknown Datasource Name", + "description": "Tests panel with unknown datasource - should preserve as UID-only reference", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "unknown-target-datasource" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with Expression Query", + "description": "Tests panel with expression query - should not inherit expression as panel datasource", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "__expr__", + "version": "v0", + "datasource": { + "name": "__expr__" + }, + "spec": {} + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel Inheriting from Target", + "description": "Tests panel inheriting datasource from target when panel datasource was default", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with Named Datasource", + "description": "Tests panel with datasource referenced by name - should migrate to full object", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Simple Row Panel", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row with Nested Panels", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-14" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Datasource Reference Migration Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_null", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_named", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_uid", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var_unknown", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "never", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "unknown-datasource" + }, + "spec": {} + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "ConstantVariable", + "spec": { + "name": "non_query_var", + "query": "", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v0alpha1.json new file mode 100644 index 00000000000..074c0ef162a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v0alpha1.json @@ -0,0 +1,174 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v37.legend_normalization.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "id": 1, + "options": { + "legend": true + }, + "title": "Panel with Boolean Legend True", + "type": "timeseries" + }, + { + "id": 2, + "options": { + "legend": false + }, + "title": "Panel with Boolean Legend False", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "id": 3, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "Panel with Hidden DisplayMode", + "type": "timeseries" + }, + { + "id": 4, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "Panel with ShowLegend False", + "type": "stat" + }, + { + "id": 5, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true + } + }, + "title": "Panel with Table Legend", + "type": "barchart" + }, + { + "id": 6, + "options": { + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + } + }, + "title": "Panel with List Legend", + "type": "histogram" + }, + { + "id": 7, + "title": "Panel with No Options", + "type": "text" + }, + { + "id": 8, + "options": { + "reduceOptions": { + "fields": "/.*temperature.*/" + } + }, + "title": "Panel with No Legend Config", + "type": "gauge" + }, + { + "id": 9, + "options": {}, + "title": "Panel with Null Legend", + "type": "piechart" + }, + { + "collapsed": false, + "id": 10, + "panels": [ + { + "id": 11, + "options": { + "legend": true + }, + "title": "Nested Panel with Boolean Legend", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "id": 12, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "Nested Panel with Hidden DisplayMode", + "type": "timeseries" + }, + { + "id": 13, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "Nested Panel with Conflicting Properties", + "type": "stat" + } + ], + "title": "Row with Nested Panels Having Various Legend Configs", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V37 Legend Normalization Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json new file mode 100644 index 00000000000..55f6c000481 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json @@ -0,0 +1,633 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v37.legend_normalization.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with Boolean Legend True", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Nested Panel with Boolean Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Nested Panel with Hidden DisplayMode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Nested Panel with Conflicting Properties", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with Boolean Legend False", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": false + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with Hidden DisplayMode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with ShowLegend False", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with Table Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with List Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "histogram", + "spec": { + "pluginVersion": "", + "options": { + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with No Options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with No Legend Config", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": { + "reduceOptions": { + "fields": "/.*temperature.*/" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with Null Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "piechart", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Panels Having Various Legend Configs", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V37 Legend Normalization Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json new file mode 100644 index 00000000000..2803cc68947 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json @@ -0,0 +1,646 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v37.legend_normalization.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with Boolean Legend True", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Nested Panel with Boolean Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Nested Panel with Hidden DisplayMode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "id": 13, + "title": "Nested Panel with Conflicting Properties", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with Boolean Legend False", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": false + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with Hidden DisplayMode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with ShowLegend False", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with Table Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with List Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "histogram", + "version": "", + "spec": { + "options": { + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with No Options", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with No Legend Config", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": { + "reduceOptions": { + "fields": "/.*temperature.*/" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Panel with Null Legend", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "piechart", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Panels Having Various Legend Configs", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-13" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V37 Legend Normalization Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v0alpha1.json new file mode 100644 index 00000000000..2bb56b9a8a3 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v0alpha1.json @@ -0,0 +1,268 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v38.table_displaymode_comprehensive.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 1, + "title": "Table with Basic Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 2, + "title": "Table with Gradient Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 3, + "title": "Table with LCD Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } + } + }, + "overrides": [] + }, + "id": 4, + "title": "Table with Color Background", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" + } + } + }, + "overrides": [] + }, + "id": 5, + "title": "Table with Color Background Solid", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" + } + } + }, + "overrides": [] + }, + "id": 6, + "title": "Table with Unknown Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + }, + "overrides": [] + }, + "id": 7, + "title": "Table with No Display Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + } + ] + }, + "id": 8, + "title": "Table with Overrides", + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "id": 9, + "title": "Non-table Panel (Should Remain Unchanged)", + "type": "timeseries" + }, + { + "collapsed": false, + "id": 10, + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 11, + "title": "Nested Table with Basic Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + }, + "id": 12, + "title": "Nested Table with Gradient Gauge", + "type": "table" + } + ], + "title": "Row with Nested Table Panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V38 Table Migration Comprehensive Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json new file mode 100644 index 00000000000..66bb53a1033 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json @@ -0,0 +1,663 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v38.table_displaymode_comprehensive.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Table with Basic Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Nested Table with Basic Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Nested Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Table with LCD Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Table with Color Background", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Table with Color Background Solid", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Table with Unknown Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Table with No Display Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Table with Overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + } + ] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Non-table Panel (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Table Panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V38 Table Migration Comprehensive Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json new file mode 100644 index 00000000000..493b6147404 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json @@ -0,0 +1,675 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v38.table_displaymode_comprehensive.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Table with Basic Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Nested Table with Basic Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Nested Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Table with LCD Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Table with Color Background", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Table with Color Background Solid", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Table with Unknown Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Table with No Display Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Table with Overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + } + ] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Non-table Panel (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Table Panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V38 Table Migration Comprehensive Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v0alpha1.json new file mode 100644 index 00000000000..98e6f272216 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v0alpha1.json @@ -0,0 +1,268 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v38.timeseries_table_display_mode.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 1, + "title": "Table with Basic Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 2, + "title": "Table with Gradient Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 3, + "title": "Table with LCD Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } + } + }, + "overrides": [] + }, + "id": 4, + "title": "Table with Color Background", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" + } + } + }, + "overrides": [] + }, + "id": 5, + "title": "Table with Color Background Solid", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" + } + } + }, + "overrides": [] + }, + "id": 6, + "title": "Table with Unknown Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + }, + "overrides": [] + }, + "id": 7, + "title": "Table with No Display Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + } + ] + }, + "id": 8, + "title": "Table with Overrides", + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "id": 9, + "title": "Non-table Panel (Should Remain Unchanged)", + "type": "timeseries" + }, + { + "collapsed": false, + "id": 10, + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + }, + "id": 11, + "title": "Nested Table with Basic Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + }, + "id": 12, + "title": "Nested Table with Gradient Gauge", + "type": "table" + } + ], + "title": "Row with Nested Table Panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V38 Table Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json new file mode 100644 index 00000000000..52b313e6b4b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json @@ -0,0 +1,663 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v38.timeseries_table_display_mode.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Table with Basic Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Nested Table with Basic Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Nested Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Table with LCD Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Table with Color Background", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Table with Color Background Solid", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Table with Unknown Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Table with No Display Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Table with Overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + } + ] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Non-table Panel (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Table Panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V38 Table Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json new file mode 100644 index 00000000000..81cc875df07 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json @@ -0,0 +1,675 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v38.timeseries_table_display_mode.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Table with Basic Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "id": 11, + "title": "Nested Table with Basic Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-12": { + "kind": "Panel", + "spec": { + "id": 12, + "title": "Nested Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Table with Gradient Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Table with LCD Gauge", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Table with Color Background", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Table with Color Background Solid", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Table with Unknown Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Table with No Display Mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + }, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Table with Overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + } + ] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "Non-table Panel (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Table Panels", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-11" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-12" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V38 Table Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v0alpha1.json new file mode 100644 index 00000000000..c847c72890b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v0alpha1.json @@ -0,0 +1,205 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v39.transform_timeseries_table.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "id": 1, + "title": "Panel with TimeSeriesTable Transformation - Single Stat", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + } + } + } + ], + "type": "table" + }, + { + "id": 2, + "title": "Panel with TimeSeriesTable Transformation - Multiple Stats", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + }, + "B": { + "stat": "max" + }, + "C": { + "stat": "min" + }, + "D": { + "stat": "sum" + } + } + } + ], + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "id": 3, + "title": "Panel with TimeSeriesTable Transformation - Mixed with Other Transforms", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "mean" + ] + } + }, + { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "last" + }, + "B": { + "stat": "first" + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": {} + } + } + ], + "type": "timeseries" + }, + { + "id": 4, + "title": "Panel with Non-TimeSeriesTable Transformation (Should Remain Unchanged)", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "mean", + "max" + ] + } + } + ], + "type": "stat" + }, + { + "id": 5, + "title": "Panel with TimeSeriesTable - Empty RefIdToStat", + "transformations": [ + { + "id": "timeSeriesTable", + "options": {} + } + ], + "type": "table" + }, + { + "id": 6, + "title": "Panel with TimeSeriesTable - No Options (Should Skip)", + "transformations": [ + { + "id": "timeSeriesTable" + } + ], + "type": "table" + }, + { + "id": 7, + "title": "Panel with TimeSeriesTable - Invalid Options (Should Skip)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "someOtherOption": "value" + } + } + ], + "type": "table" + }, + { + "autoMigrateFrom": "graph", + "id": 8, + "title": "Panel with No Transformations (Should Remain Unchanged)", + "type": "timeseries" + }, + { + "collapsed": false, + "id": 9, + "panels": [ + { + "id": 10, + "title": "Nested Panel with TimeSeriesTable", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "NestedA": { + "stat": "median" + }, + "NestedB": { + "stat": "stdDev" + } + } + } + ], + "type": "table" + } + ], + "title": "Row with Nested Panels Having TimeSeriesTable Transformations", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json new file mode 100644 index 00000000000..df196e14beb --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json @@ -0,0 +1,590 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v39.transform_timeseries_table.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with TimeSeriesTable Transformation - Single Stat", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Nested Panel with TimeSeriesTable", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "NestedA": { + "stat": "median" + }, + "NestedB": { + "stat": "stdDev" + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with TimeSeriesTable Transformation - Multiple Stats", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + }, + "B": { + "stat": "max" + }, + "C": { + "stat": "min" + }, + "D": { + "stat": "sum" + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with TimeSeriesTable Transformation - Mixed with Other Transforms", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "mean" + ] + } + } + }, + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "last" + }, + "B": { + "stat": "first" + } + } + } + }, + { + "kind": "organize", + "spec": { + "id": "organize", + "options": { + "excludeByName": {} + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with Non-TimeSeriesTable Transformation (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "mean", + "max" + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with TimeSeriesTable - Empty RefIdToStat", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with TimeSeriesTable - No Options (Should Skip)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": null + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with TimeSeriesTable - Invalid Options (Should Skip)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "someOtherOption": "value" + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with No Transformations (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Panels Having TimeSeriesTable Transformations", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json new file mode 100644 index 00000000000..3b56960e026 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json @@ -0,0 +1,600 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v39.transform_timeseries_table.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with TimeSeriesTable Transformation - Single Stat", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Nested Panel with TimeSeriesTable", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "NestedA": { + "stat": "median" + }, + "NestedB": { + "stat": "stdDev" + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with TimeSeriesTable Transformation - Multiple Stats", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + }, + "B": { + "stat": "max" + }, + "C": { + "stat": "min" + }, + "D": { + "stat": "sum" + } + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel with TimeSeriesTable Transformation - Mixed with Other Transforms", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "mean" + ] + } + } + }, + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "last" + }, + "B": { + "stat": "first" + } + } + } + }, + { + "kind": "organize", + "spec": { + "id": "organize", + "options": { + "excludeByName": {} + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel with Non-TimeSeriesTable Transformation (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "mean", + "max" + ] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel with TimeSeriesTable - Empty RefIdToStat", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": {} + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with TimeSeriesTable - No Options (Should Skip)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": null + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with TimeSeriesTable - Invalid Options (Should Skip)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [ + { + "kind": "timeSeriesTable", + "spec": { + "id": "timeSeriesTable", + "options": { + "someOtherOption": "value" + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "id": 8, + "title": "Panel with No Transformations (Should Remain Unchanged)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-8" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with Nested Panels Having TimeSeriesTable Transformations", + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v0alpha1.json new file mode 100644 index 00000000000..a41d82c8feb --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v0alpha1.json @@ -0,0 +1,111 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v4.no-op.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V4 No-Op Migration Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json new file mode 100644 index 00000000000..1a3bd5c0e72 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json @@ -0,0 +1,241 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v4.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V4 No-Op Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json new file mode 100644 index 00000000000..b6d1aa7d200 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json @@ -0,0 +1,248 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v4.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V4 No-Op Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v0alpha1.json new file mode 100644 index 00000000000..a68f9fdd2e4 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v40.refresh_empty_string.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Empty String Refresh Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json new file mode 100644 index 00000000000..db8e3a3542a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v40.refresh_empty_string.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Empty String Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json new file mode 100644 index 00000000000..a6cabe62d43 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v40.refresh_empty_string.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Empty String Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v0alpha1.json new file mode 100644 index 00000000000..ee3991c8f2a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v40.refresh_false.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Boolean False Refresh Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json new file mode 100644 index 00000000000..bd834847e28 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v40.refresh_false.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Boolean False Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json new file mode 100644 index 00000000000..7b5f900ef36 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v40.refresh_false.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Boolean False Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v0alpha1.json new file mode 100644 index 00000000000..a6063fed38e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v40.refresh_not_set.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Refresh Not Set Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json new file mode 100644 index 00000000000..8279dfd765b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v40.refresh_not_set.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Refresh Not Set Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json new file mode 100644 index 00000000000..1f09949f995 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v40.refresh_not_set.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Refresh Not Set Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v0alpha1.json new file mode 100644 index 00000000000..fbd4b0e1486 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v40.refresh_numeric.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Numeric Refresh Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json new file mode 100644 index 00000000000..2cc922fc5f5 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v40.refresh_numeric.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Numeric Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json new file mode 100644 index 00000000000..6904267e72b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v40.refresh_numeric.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Numeric Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v0alpha1.json new file mode 100644 index 00000000000..d1f0fe07cd7 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v40.refresh_string.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "1m", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "String Refresh Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json new file mode 100644 index 00000000000..d9347950655 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v40.refresh_string.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "1m", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "String Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json new file mode 100644 index 00000000000..b1bd81269c1 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v40.refresh_string.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "1m", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "String Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v0alpha1.json new file mode 100644 index 00000000000..e7103268a45 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v40.refresh_true.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Boolean Refresh Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json new file mode 100644 index 00000000000..9601af1797a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v40.refresh_true.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Boolean Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json new file mode 100644 index 00000000000..76eea7dbbdc --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v40.refresh_true.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Boolean Refresh Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v0alpha1.json new file mode 100644 index 00000000000..0dbd900c8ef --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v0alpha1.json @@ -0,0 +1,50 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v41.no_time_picker.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "No Time Picker Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json new file mode 100644 index 00000000000..6f34667e603 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v41.no_time_picker.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Time Picker Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json new file mode 100644 index 00000000000..54e2cfeef43 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v41.no_time_picker.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Time Picker Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v0alpha1.json new file mode 100644 index 00000000000..52099b239f3 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v0alpha1.json @@ -0,0 +1,62 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v41.time_picker_no_time_options.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Time Picker No Time Options Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json new file mode 100644 index 00000000000..46a07c16b0e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v41.time_picker_no_time_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Time Picker No Time Options Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json new file mode 100644 index 00000000000..52de1909de4 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v41.time_picker_no_time_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Time Picker No Time Options Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v0alpha1.json new file mode 100644 index 00000000000..739677d77f5 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v0alpha1.json @@ -0,0 +1,63 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v41.time_picker_time_options.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Time Picker Time Options Test Dashboard", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json new file mode 100644 index 00000000000..15368de002c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json @@ -0,0 +1,68 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v41.time_picker_time_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Time Picker Time Options Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json new file mode 100644 index 00000000000..9dffb1fc27e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json @@ -0,0 +1,69 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v41.time_picker_time_options.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Time Picker Time Options Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v0alpha1.json new file mode 100644 index 00000000000..94d5e062fd1 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v0alpha1.json @@ -0,0 +1,75 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v42.harky_must.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 1, + "title": "Panel with hideFrom.viz = true", + "type": "timeseries" + } + ], + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "v42 Migration Test - Harky Must", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json new file mode 100644 index 00000000000..d2e8c73301e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json @@ -0,0 +1,127 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v42.harky_must.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with hideFrom.viz = true", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "v42 Migration Test - Harky Must", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json new file mode 100644 index 00000000000..3d267c3135b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json @@ -0,0 +1,129 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v42.harky_must.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with hideFrom.viz = true", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "v42 Migration Test - Harky Must", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v0alpha1.json new file mode 100644 index 00000000000..eb821d0672c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v0alpha1.json @@ -0,0 +1,233 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v42.hidefrom_tooltip.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 1, + "title": "Panel with hideFrom.viz = true", + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + }, + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "foo" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 2, + "title": "Panel with multiple overrides", + "type": "timeseries" + }, + { + "collapsed": true, + "id": 3, + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*/" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 4, + "title": "Nested panel with hideFrom", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + }, + "id": 5, + "title": "Panel without hideFrom", + "type": "table" + } + ], + "title": "Row with nested panels", + "type": "row" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + }, + "id": 6, + "title": "Panel with viz false (should not be modified)", + "type": "gauge" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 7, + "title": "Panel with already set tooltip (should not be modified)", + "type": "barchart" + } + ], + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "v42 Migration Test - HideFrom Tooltip", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json new file mode 100644 index 00000000000..9ef09aa0a9b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json @@ -0,0 +1,465 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v42.hidefrom_tooltip.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with hideFrom.viz = true", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with multiple overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + }, + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "foo" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Nested panel with hideFrom", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*/" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel without hideFrom", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with viz false (should not be modified)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "gauge", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with already set tooltip (should not be modified)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with nested panels", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "v42 Migration Test - HideFrom Tooltip", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json new file mode 100644 index 00000000000..a1e187676a5 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json @@ -0,0 +1,472 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v42.hidefrom_tooltip.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with hideFrom.viz = true", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with multiple overrides", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + }, + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "foo" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Nested panel with hideFrom", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*/" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel without hideFrom", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Panel with viz false (should not be modified)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "gauge", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Panel with already set tooltip (should not be modified)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": true, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row with nested panels", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "v42 Migration Test - HideFrom Tooltip", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v0alpha1.json new file mode 100644 index 00000000000..706040137db --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v0alpha1.json @@ -0,0 +1,111 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v5.no-op.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V5 No-Op Migration Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json new file mode 100644 index 00000000000..5855379dc98 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json @@ -0,0 +1,241 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v5.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V5 No-Op Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json new file mode 100644 index 00000000000..10544c3c0fe --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json @@ -0,0 +1,248 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v5.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V5 No-Op Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v0alpha1.json new file mode 100644 index 00000000000..75a9fa83f62 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v0alpha1.json @@ -0,0 +1,171 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v6.pulldowns_and_templating.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": true, + "iconColor": "red", + "name": "deployment", + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + }, + { + "datasource": { + "type": "loki", + "uid": "loki-uid" + }, + "enable": false, + "iconColor": "yellow", + "name": "alerts", + "query": "{job=\"alertmanager\"}" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}", + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "memory_usage{region=\"$region\"}", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "refresh": "30s", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "name": "environment", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + }, + { + "allFormat": "glob", + "datasource": "prometheus", + "name": "service", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + }, + { + "name": "region", + "options": [ + { + "text": "us-east-1", + "value": "us-east-1" + }, + { + "text": "us-west-2", + "value": "us-west-2" + } + ], + "type": "custom" + }, + { + "datasource": "prometheus", + "name": "instance", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V6 Pulldowns and Template Variables Migration Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json new file mode 100644 index 00000000000..de1bac8f2f5 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json @@ -0,0 +1,336 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v6.pulldowns_and_templating.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "deployment", + "legacyOptions": { + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "loki", + "uid": "loki-uid" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "yellow", + "name": "alerts", + "legacyOptions": { + "query": "{job=\"alertmanager\"}" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "memory_usage{region=\"$region\"}" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-1h", + "to": "now", + "autoRefresh": "30s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V6 Pulldowns and Template Variables Migration Test", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "environment", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "service", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "region", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "us-east-1", + "value": "us-east-1" + }, + { + "selected": false, + "text": "us-west-2", + "value": "us-west-2" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "instance", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json new file mode 100644 index 00000000000..3562d0154e1 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json @@ -0,0 +1,348 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v6.pulldowns_and_templating.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "deployment", + "legacyOptions": { + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "loki-uid" + }, + "spec": {} + }, + "enable": false, + "hide": false, + "iconColor": "yellow", + "name": "alerts", + "legacyOptions": { + "query": "{job=\"alertmanager\"}" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "CPU Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Memory Usage", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "memory_usage{region=\"$region\"}" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-1h", + "to": "now", + "autoRefresh": "30s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V6 Pulldowns and Template Variables Migration Test", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "name": "environment", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "service", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "region", + "query": "", + "current": { + "text": "", + "value": "" + }, + "options": [ + { + "selected": false, + "text": "us-east-1", + "value": "us-east-1" + }, + { + "selected": false, + "text": "us-west-2", + "value": "us-west-2" + } + ], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "instance", + "current": { + "text": "", + "value": "" + }, + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v0alpha1.json new file mode 100644 index 00000000000..4bb069c11a3 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v0alpha1.json @@ -0,0 +1,98 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v7.timepicker.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "up", + "refId": "A" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage", + "refId": "B" + } + ] + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "collapse": false, + "enable": true, + "notice": false, + "now": true, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "status": "Stable", + "type": "timepicker" + }, + "timezone": "", + "title": "No Title", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json new file mode 100644 index 00000000000..48cdb34c46b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json @@ -0,0 +1,146 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v7.timepicker.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "up" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "cpu_usage" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Title", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json new file mode 100644 index 00000000000..72cb9aadb9b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json @@ -0,0 +1,150 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v7.timepicker.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "up" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "cpu_usage" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "No Title", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v0alpha1.json new file mode 100644 index 00000000000..95b06ba7ef4 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v0alpha1.json @@ -0,0 +1,156 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v8.influxdb_query.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "groupBy": [ + { + "params": [ + "1m" + ], + "type": "time" + }, + { + "params": [ + "host" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + }, + { + "params": [ + "*2" + ], + "type": "math" + }, + { + "params": [ + "doubled" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "value": "server1" + } + ] + } + ], + "type": "timeseries" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "groupBy": [ + { + "interval": "1m", + "type": "time" + } + ], + "rawQuery": true, + "refId": "A", + "tags": [ + { + "key": "host", + "value": "server1" + } + ] + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V8 InfluxDB Query Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json new file mode 100644 index 00000000000..75a2970f8b6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json @@ -0,0 +1,251 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v8.influxdb_query.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "groupBy": [ + { + "params": [ + "1m" + ], + "type": "time" + }, + { + "params": [ + "host" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + }, + { + "params": [ + "*2" + ], + "type": "math" + }, + { + "params": [ + "doubled" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "value": "server1" + } + ] + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "groupBy": [ + { + "interval": "1m", + "type": "time" + } + ], + "rawQuery": true, + "tags": [ + { + "key": "host", + "value": "server1" + } + ] + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V8 InfluxDB Query Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json new file mode 100644 index 00000000000..d8ee1076605 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json @@ -0,0 +1,256 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v8.influxdb_query.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "groupBy": [ + { + "params": [ + "1m" + ], + "type": "time" + }, + { + "params": [ + "host" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + }, + { + "params": [ + "*2" + ], + "type": "math" + }, + { + "params": [ + "doubled" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "value": "server1" + } + ] + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "groupBy": [ + { + "interval": "1m", + "type": "time" + } + ], + "rawQuery": true, + "tags": [ + { + "key": "host", + "value": "server1" + } + ] + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V8 InfluxDB Query Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v0alpha1.json new file mode 100644 index 00000000000..4c7255237ba --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v0alpha1.json @@ -0,0 +1,111 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "v9.no-op.v42" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V9 No-Op Migration Test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json new file mode 100644 index 00000000000..472d28ffa99 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json @@ -0,0 +1,241 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "v9.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V9 No-Op Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json new file mode 100644 index 00000000000..45487aaebb9 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json @@ -0,0 +1,248 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "v9.no-op.v42" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 6, + "height": 3, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "V9 No-Op Migration Test", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json new file mode 100644 index 00000000000..1f0c641c7a6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json @@ -0,0 +1,230 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "annotation-conversions-test", + "labels": { + "test": "annotation-conversions" + } + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "builtIn": 0, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Prometheus Annotations", + "target": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + }, + "type": "prometheus" + }, + { + "builtIn": false, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "TestData Annotations", + "target": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + }, + "type": "testdata" + }, + { + "builtIn": 0, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "Elasticsearch Annotations", + "tagsField": "tags", + "target": { + "query": "tags:deployment", + "refId": "Anno", + "timeField": "@timestamp" + }, + "textField": "message", + "timeEndField": "end_time", + "timeField": "@timestamp", + "type": "elasticsearch" + }, + { + "builtIn": 0, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "enable": true, + "hide": false, + "iconColor": "green", + "name": "SQL Annotations", + "tagsColumn": "tags", + "target": { + "format": "table", + "rawSql": "SELECT time, title, text, tags FROM annotations WHERE $__timeFilter(time)", + "refId": "Anno" + }, + "textColumn": "text", + "timeColumn": "time", + "titleColumn": "title", + "type": "sql" + }, + { + "builtIn": 0, + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "No Datasource Annotation", + "target": { + "expr": "static_annotation_query", + "refId": "Anno" + }, + "type": "manual" + }, + { + "builtIn": 0, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "enable": false, + "hide": true, + "iconColor": "gray", + "name": "Hidden Annotation", + "target": { + "expr": "disabled_query", + "refId": "Anno" + }, + "type": "prometheus" + }, + { + "builtIn": 0, + "datasource": { + "type": "influxdb", + "uid": "influx-uid" + }, + "enable": true, + "filter": { + "exclude": true, + "ids": [ + 1, + 2, + 3 + ] + }, + "hide": false, + "iconColor": "#FF5722", + "mappings": { + "tags": "labels", + "text": "description", + "time": "timestamp", + "title": "service" + }, + "name": "Complex Filter Annotation", + "target": { + "query": "from(bucket: \"events\") |\u003e range(start: $__timeFrom, stop: $__timeTo) |\u003e filter(fn: (r) =\u003e r._measurement == \"deployments\")", + "refId": "Anno" + }, + "type": "influxdb" + }, + { + "builtIn": 0, + "datasource": { + "type": "cloudwatch", + "uid": "cloudwatch-uid" + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotations", + "showIn": 0, + "target": { + "dimensions": { + "InstanceId": "i-1234567890abcdef" + }, + "metricName": "CPUUtilization", + "namespace": "AWS/EC2", + "refId": "Anno", + "statistic": "Average" + }, + "type": "cloudwatch" + }, + { + "builtIn": true, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": false, + "hide": true, + "iconColor": "transparent", + "name": "Disabled Built-in Annotation", + "target": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + }, + "type": "prometheus" + } + ] + }, + "description": "Testing annotation transformations", + "links": [], + "panels": [], + "tags": [ + "test", + "annotations" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "title": "Annotation Conversions Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json new file mode 100644 index 00000000000..9324355a72f --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json @@ -0,0 +1,327 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "annotation-conversions-test", + "labels": { + "test": "annotation-conversions" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "DataQuery", + "spec": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + } + }, + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + } + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Prometheus Annotations", + "builtIn": false, + "legacyOptions": { + "type": "prometheus" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "TestData Annotations", + "builtIn": false, + "legacyOptions": { + "type": "testdata" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "query": "tags:deployment", + "refId": "Anno", + "timeField": "@timestamp" + } + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "Elasticsearch Annotations", + "builtIn": false, + "legacyOptions": { + "tagsField": "tags", + "textField": "message", + "timeEndField": "end_time", + "timeField": "@timestamp", + "type": "elasticsearch" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "format": "table", + "rawSql": "SELECT time, title, text, tags FROM annotations WHERE $__timeFilter(time)", + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "green", + "name": "SQL Annotations", + "builtIn": false, + "legacyOptions": { + "tagsColumn": "tags", + "textColumn": "text", + "timeColumn": "time", + "titleColumn": "title", + "type": "sql" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "static_annotation_query", + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "No Datasource Annotation", + "builtIn": false, + "legacyOptions": { + "type": "manual" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "expr": "disabled_query", + "refId": "Anno" + } + }, + "enable": false, + "hide": true, + "iconColor": "gray", + "name": "Hidden Annotation", + "builtIn": false, + "legacyOptions": { + "type": "prometheus" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "influxdb", + "uid": "influx-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "query": "from(bucket: \"events\") |\u003e range(start: $__timeFrom, stop: $__timeTo) |\u003e filter(fn: (r) =\u003e r._measurement == \"deployments\")", + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "#FF5722", + "name": "Complex Filter Annotation", + "builtIn": false, + "filter": { + "exclude": true, + "ids": [ + 1, + 2, + 3 + ] + }, + "legacyOptions": { + "mappings": { + "tags": "labels", + "text": "description", + "time": "timestamp", + "title": "service" + }, + "type": "influxdb" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "cloudwatch", + "uid": "cloudwatch-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "dimensions": { + "InstanceId": "i-1234567890abcdef" + }, + "metricName": "CPUUtilization", + "namespace": "AWS/EC2", + "refId": "Anno", + "statistic": "Average" + } + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotations", + "builtIn": false, + "legacyOptions": { + "showIn": 0, + "type": "cloudwatch" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + } + }, + "enable": false, + "hide": true, + "iconColor": "transparent", + "name": "Disabled Built-in Annotation", + "builtIn": true, + "legacyOptions": { + "type": "prometheus" + } + } + } + ], + "cursorSync": "Off", + "description": "Testing annotation transformations", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "annotations" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-24h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Annotation Conversions Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json new file mode 100644 index 00000000000..3f0eb4d3eb4 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json @@ -0,0 +1,338 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "annotation-conversions-test", + "labels": { + "test": "annotation-conversions" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + } + }, + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + } + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Prometheus Annotations", + "builtIn": false, + "legacyOptions": { + "type": "prometheus" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "TestData Annotations", + "builtIn": false, + "legacyOptions": { + "type": "testdata" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": { + "query": "tags:deployment", + "refId": "Anno", + "timeField": "@timestamp" + } + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "Elasticsearch Annotations", + "builtIn": false, + "legacyOptions": { + "tagsField": "tags", + "textField": "message", + "timeEndField": "end_time", + "timeField": "@timestamp", + "type": "elasticsearch" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": { + "format": "table", + "rawSql": "SELECT time, title, text, tags FROM annotations WHERE $__timeFilter(time)", + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "green", + "name": "SQL Annotations", + "builtIn": false, + "legacyOptions": { + "tagsColumn": "tags", + "textColumn": "text", + "timeColumn": "time", + "titleColumn": "title", + "type": "sql" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "expr": "static_annotation_query", + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "No Datasource Annotation", + "builtIn": false, + "legacyOptions": { + "type": "manual" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "expr": "disabled_query", + "refId": "Anno" + } + }, + "enable": false, + "hide": true, + "iconColor": "gray", + "name": "Hidden Annotation", + "builtIn": false, + "legacyOptions": { + "type": "prometheus" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "influxdb", + "version": "v0", + "datasource": { + "name": "influx-uid" + }, + "spec": { + "query": "from(bucket: \"events\") |\u003e range(start: $__timeFrom, stop: $__timeTo) |\u003e filter(fn: (r) =\u003e r._measurement == \"deployments\")", + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "#FF5722", + "name": "Complex Filter Annotation", + "builtIn": false, + "filter": { + "exclude": true, + "ids": [ + 1, + 2, + 3 + ] + }, + "legacyOptions": { + "mappings": { + "tags": "labels", + "text": "description", + "time": "timestamp", + "title": "service" + }, + "type": "influxdb" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "cloudwatch", + "version": "v0", + "datasource": { + "name": "cloudwatch-uid" + }, + "spec": { + "dimensions": { + "InstanceId": "i-1234567890abcdef" + }, + "metricName": "CPUUtilization", + "namespace": "AWS/EC2", + "refId": "Anno", + "statistic": "Average" + } + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotations", + "builtIn": false, + "legacyOptions": { + "showIn": 0, + "type": "cloudwatch" + } + } + }, + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + } + }, + "enable": false, + "hide": true, + "iconColor": "transparent", + "name": "Disabled Built-in Annotation", + "builtIn": true, + "legacyOptions": { + "type": "prometheus" + } + } + } + ], + "cursorSync": "Off", + "description": "Testing annotation transformations", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "annotations" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-24h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Annotation Conversions Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v0alpha1.json new file mode 100644 index 00000000000..75dd241b415 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v0alpha1.json @@ -0,0 +1,210 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "dashboard-properties-test", + "labels": { + "test": "dashboard-properties" + } + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 0, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "Simple Annotation", + "target": { + "refId": "Anno" + } + } + ] + }, + "description": "Testing dashboard-level property transformations including time settings, cursor sync, and links", + "editable": true, + "fiscalYearStartMonth": 4, + "gnetId": 12345, + "graphTooltip": 2, + "iteration": 1640995200000, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "monitoring", + "alerts" + ], + "targetBlank": true, + "title": "External Monitoring System", + "tooltip": "View in external system", + "type": "link", + "url": "https://monitoring.example.com/dashboard?from=${__from}\u0026to=${__to}" + }, + { + "asDropdown": true, + "icon": "dashboard", + "includeVars": false, + "keepTime": false, + "tags": [ + "grafana", + "internal" + ], + "targetBlank": false, + "title": "Related Dashboards", + "tooltip": "Navigate to related dashboards", + "type": "dashboards", + "url": "" + }, + { + "asDropdown": false, + "icon": "info", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Documentation", + "tooltip": "View documentation", + "type": "link", + "url": "https://docs.example.com/dashboard-guide" + }, + { + "asDropdown": false, + "icon": "tag", + "includeVars": true, + "keepTime": true, + "tags": [ + "alerts" + ], + "targetBlank": false, + "title": "Tag-based Link", + "tooltip": "Filtered by tags", + "type": "tag", + "url": "/dashboards/tag/alerts" + } + ], + "liveNow": true, + "meta": { + "canAdmin": false, + "canEdit": true, + "canSave": true, + "canStar": true, + "created": "2023-01-01T00:00:00Z", + "createdBy": "admin", + "expires": "2024-01-01T00:00:00Z", + "folderId": 0, + "folderTitle": "General", + "folderUrl": "", + "hasAcl": false, + "isFolder": false, + "provisioned": false, + "provisionedExternalId": "", + "slug": "dashboard-properties-test", + "updated": "2023-06-01T00:00:00Z", + "updatedBy": "admin", + "url": "/d/test/dashboard-properties-test", + "version": 42 + }, + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "targets": [ + { + "refId": "A" + } + ], + "title": "Simple Panel", + "type": "stat" + } + ], + "preload": true, + "refresh": "30s", + "schemaVersion": 39, + "snapshot": { + "originalUrl": "http://localhost:3000/d/test", + "timestamp": "2023-01-01T00:00:00Z" + }, + "style": "dark", + "tags": [ + "test", + "properties", + "metadata" + ], + "templating": { + "list": [ + { + "current": { + "text": "value1", + "value": "value1" + }, + "hide": 0, + "name": "simple_var", + "query": "value1,value2,value3", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-12h", + "to": "now" + }, + "timepicker": { + "collapse": false, + "enable": true, + "hidden": false, + "notice": false, + "now": true, + "nowDelay": "1m", + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "status": "Stable", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "type": "timepicker" + }, + "timezone": "America/New_York", + "title": "Dashboard Properties Test", + "version": 42, + "weekStart": "sunday" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json new file mode 100644 index 00000000000..40810adb95b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json @@ -0,0 +1,203 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "dashboard-properties-test", + "labels": { + "test": "dashboard-properties" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "Simple Annotation", + "builtIn": false + } + } + ], + "cursorSync": "Tooltip", + "description": "Testing dashboard-level property transformations including time settings, cursor sync, and links", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Simple Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [ + { + "title": "External Monitoring System", + "type": "link", + "icon": "external link", + "tooltip": "View in external system", + "url": "https://monitoring.example.com/dashboard?from=${__from}\u0026to=${__to}", + "tags": [ + "monitoring", + "alerts" + ], + "asDropdown": false, + "targetBlank": true, + "includeVars": true, + "keepTime": true + }, + { + "title": "Related Dashboards", + "type": "dashboards", + "icon": "dashboard", + "tooltip": "Navigate to related dashboards", + "url": "", + "tags": [ + "grafana", + "internal" + ], + "asDropdown": true, + "targetBlank": false, + "includeVars": false, + "keepTime": false + }, + { + "title": "Documentation", + "type": "link", + "icon": "info", + "tooltip": "View documentation", + "url": "https://docs.example.com/dashboard-guide", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Tag-based Link", + "type": "tag", + "icon": "tag", + "tooltip": "Filtered by tags", + "url": "/dashboards/tag/alerts", + "tags": [ + "alerts" + ], + "asDropdown": false, + "targetBlank": false, + "includeVars": true, + "keepTime": true + } + ], + "liveNow": true, + "preload": true, + "tags": [ + "test", + "properties", + "metadata" + ], + "timeSettings": { + "timezone": "America/New_York", + "from": "now-12h", + "to": "now", + "autoRefresh": "30s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "weekStart": "sunday", + "fiscalYearStartMonth": 4, + "nowDelay": "1m" + }, + "title": "Dashboard Properties Test", + "variables": [ + { + "kind": "CustomVariable", + "spec": { + "name": "simple_var", + "query": "value1,value2,value3", + "current": { + "text": "value1", + "value": "value1" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json new file mode 100644 index 00000000000..433badc2f16 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json @@ -0,0 +1,208 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "dashboard-properties-test", + "labels": { + "test": "dashboard-properties" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": { + "refId": "Anno" + } + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "Simple Annotation", + "builtIn": false + } + } + ], + "cursorSync": "Tooltip", + "description": "Testing dashboard-level property transformations including time settings, cursor sync, and links", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Simple Panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [ + { + "title": "External Monitoring System", + "type": "link", + "icon": "external link", + "tooltip": "View in external system", + "url": "https://monitoring.example.com/dashboard?from=${__from}\u0026to=${__to}", + "tags": [ + "monitoring", + "alerts" + ], + "asDropdown": false, + "targetBlank": true, + "includeVars": true, + "keepTime": true + }, + { + "title": "Related Dashboards", + "type": "dashboards", + "icon": "dashboard", + "tooltip": "Navigate to related dashboards", + "url": "", + "tags": [ + "grafana", + "internal" + ], + "asDropdown": true, + "targetBlank": false, + "includeVars": false, + "keepTime": false + }, + { + "title": "Documentation", + "type": "link", + "icon": "info", + "tooltip": "View documentation", + "url": "https://docs.example.com/dashboard-guide", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Tag-based Link", + "type": "tag", + "icon": "tag", + "tooltip": "Filtered by tags", + "url": "/dashboards/tag/alerts", + "tags": [ + "alerts" + ], + "asDropdown": false, + "targetBlank": false, + "includeVars": true, + "keepTime": true + } + ], + "liveNow": true, + "preload": true, + "tags": [ + "test", + "properties", + "metadata" + ], + "timeSettings": { + "timezone": "America/New_York", + "from": "now-12h", + "to": "now", + "autoRefresh": "30s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "weekStart": "sunday", + "fiscalYearStartMonth": 4, + "nowDelay": "1m" + }, + "title": "Dashboard Properties Test", + "variables": [ + { + "kind": "CustomVariable", + "spec": { + "name": "simple_var", + "query": "value1,value2,value3", + "current": { + "text": "value1", + "value": "value1" + }, + "options": [], + "multi": false, + "includeAll": false, + "hide": "dontHide", + "skipUrlSync": false, + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v0alpha1.json new file mode 100644 index 00000000000..868eea5ca2e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v0alpha1.json @@ -0,0 +1,353 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "panel-conversions-test", + "labels": { + "test": "panel-conversions" + } + }, + "spec": { + "annotations": { + "list": [] + }, + "description": "Testing panel to elements conversion", + "editable": true, + "links": [], + "panels": [ + { + "cacheTimeout": "60s", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "interval": "1m", + "links": [ + { + "targetBlank": true, + "title": "External Link", + "url": "https://example.com" + } + ], + "maxDataPoints": 300, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "expr": "up", + "hide": false, + "interval": "30s", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "expr": "cpu_usage", + "hide": true, + "interval": "1m", + "refId": "B" + } + ], + "title": "Regular Timeseries Panel", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "mean" + ] + } + } + ], + "transparent": true, + "type": "timeseries" + }, + { + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 2, + "libraryPanel": { + "name": "Shared Stats Panel", + "uid": "library-panel-uid-123" + }, + "title": "Library Panel Reference" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "edprtf91hz01se" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 3, + "panels": [], + "repeat": "server", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "edprtf91hz01se" + }, + "refId": "A" + } + ], + "title": "Row Panel", + "type": "row" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 9 + }, + "id": 4, + "maxPerRow": 4, + "repeat": "region", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "scenarioId": "random_walk" + } + ], + "title": "Panel in Row", + "type": "timeseries" + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "orientation": "horizontal" + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 9 + }, + "hideTimeOverride": true, + "id": 7, + "links": [ + { + "title": "Drill Down", + "url": "/d/detailed-view" + } + ], + "maxDataPoints": 1000, + "options": { + "barWidth": 0.97, + "groupWidth": 0.7, + "orientation": "horizontal" + }, + "pluginVersion": "9.5.0", + "queryCachingTTL": 300, + "targets": [ + { + "bucketAggs": [ + { + "field": "@timestamp", + "id": "2", + "type": "date_histogram" + } + ], + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": "*", + "refId": "A" + } + ], + "timeFrom": "1h", + "timeShift": "1h", + "title": "Complex Panel with All Features", + "transformations": [ + { + "id": "groupBy", + "options": { + "fields": { + "status": { + "aggregations": [ + "count" + ], + "operation": "groupby" + } + } + } + }, + { + "id": "sortBy", + "options": { + "sort": [ + { + "desc": true, + "field": "count" + } + ] + } + } + ], + "type": "barchart" + }, + { + "collapsed": true, + "datasource": { + "type": "prometheus", + "uid": "edprtf91hz01se" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 5, + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 6, + "options": { + "showHeader": true + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "format": "table", + "rawSql": "SELECT * FROM metrics", + "refId": "A" + } + ], + "title": "Hidden Panel in Collapsed Row", + "type": "table" + } + ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row", + "type": "row" + } + ], + "preload": false, + "tags": [ + "test", + "panels" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "title": "Panel Conversions Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json new file mode 100644 index 00000000000..a291ccbc6b2 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json @@ -0,0 +1,505 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "panel-conversions-test", + "labels": { + "test": "panel-conversions" + } + }, + "spec": { + "annotations": [], + "cursorSync": "Off", + "description": "Testing panel to elements conversion", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Regular Timeseries Panel", + "description": "", + "links": [ + { + "title": "External Link", + "url": "https://example.com", + "targetBlank": true + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "up", + "interval": "30s" + } + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "expr": "cpu_usage", + "interval": "1m" + } + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "refId": "B", + "hidden": true + } + } + ], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "mean" + ] + } + } + } + ], + "queryOptions": { + "maxDataPoints": 300, + "interval": "1m", + "cacheTimeout": "60s" + } + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "10.0.0", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1 + } + }, + "overrides": [] + } + } + }, + "transparent": true + } + }, + "panel-2": { + "kind": "LibraryPanel", + "spec": { + "id": 2, + "title": "Library Panel Reference", + "libraryPanel": { + "name": "Shared Stats Panel", + "uid": "library-panel-uid-123" + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel in Row", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "scenarioId": "random_walk" + } + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Hidden Panel in Collapsed Row", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "format": "table", + "rawSql": "SELECT * FROM metrics" + } + }, + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": { + "showHeader": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Complex Panel with All Features", + "description": "", + "links": [ + { + "title": "Drill Down", + "url": "/d/detailed-view" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "spec": { + "bucketAggs": [ + { + "field": "@timestamp", + "id": "2", + "type": "date_histogram" + } + ], + "query": "*" + } + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "groupBy", + "spec": { + "id": "groupBy", + "options": { + "fields": { + "status": { + "aggregations": [ + "count" + ], + "operation": "groupby" + } + } + } + } + }, + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "sort": [ + { + "desc": true, + "field": "count" + } + ] + } + } + } + ], + "queryOptions": { + "timeFrom": "1h", + "maxDataPoints": 1000, + "timeShift": "1h", + "queryCachingTTL": 300, + "hideTimeOverride": true + } + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "9.5.0", + "options": { + "barWidth": 0.97, + "groupWidth": 0.7, + "orientation": "horizontal" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "orientation": "horizontal" + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row Panel", + "collapse": false, + "repeat": { + "mode": "variable", + "value": "server" + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-4" + }, + "repeat": { + "mode": "variable", + "value": "region", + "direction": "h", + "maxPerRow": 4 + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 16, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "panels" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-1h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Panel Conversions Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json new file mode 100644 index 00000000000..8eac23d96c0 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json @@ -0,0 +1,514 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "panel-conversions-test", + "labels": { + "test": "panel-conversions" + } + }, + "spec": { + "annotations": [], + "cursorSync": "Off", + "description": "Testing panel to elements conversion", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Regular Timeseries Panel", + "description": "", + "links": [ + { + "title": "External Link", + "url": "https://example.com", + "targetBlank": true + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "expr": "up", + "interval": "30s" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "expr": "cpu_usage", + "interval": "1m" + } + }, + "refId": "B", + "hidden": true + } + } + ], + "transformations": [ + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "mean" + ] + } + } + } + ], + "queryOptions": { + "maxDataPoints": 300, + "interval": "1m", + "cacheTimeout": "60s" + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "10.0.0", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1 + } + }, + "overrides": [] + } + } + }, + "transparent": true + } + }, + "panel-2": { + "kind": "LibraryPanel", + "spec": { + "id": 2, + "title": "Library Panel Reference", + "libraryPanel": { + "name": "Shared Stats Panel", + "uid": "library-panel-uid-123" + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel in Row", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "default-ds-uid" + }, + "spec": { + "scenarioId": "random_walk" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Hidden Panel in Collapsed Row", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "loki", + "version": "v0", + "datasource": { + "name": "non-default-test-ds-uid" + }, + "spec": { + "format": "table", + "rawSql": "SELECT * FROM metrics" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": { + "showHeader": true + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Complex Panel with All Features", + "description": "", + "links": [ + { + "title": "Drill Down", + "url": "/d/detailed-view" + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": { + "bucketAggs": [ + { + "field": "@timestamp", + "id": "2", + "type": "date_histogram" + } + ], + "query": "*" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "groupBy", + "spec": { + "id": "groupBy", + "options": { + "fields": { + "status": { + "aggregations": [ + "count" + ], + "operation": "groupby" + } + } + } + } + }, + { + "kind": "sortBy", + "spec": { + "id": "sortBy", + "options": { + "sort": [ + { + "desc": true, + "field": "count" + } + ] + } + } + } + ], + "queryOptions": { + "timeFrom": "1h", + "maxDataPoints": 1000, + "timeShift": "1h", + "queryCachingTTL": 300, + "hideTimeOverride": true + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "9.5.0", + "spec": { + "options": { + "barWidth": 0.97, + "groupWidth": 0.7, + "orientation": "horizontal" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "orientation": "horizontal" + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "title": "", + "collapse": false, + "hideHeader": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 6, + "height": 4, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Row Panel", + "collapse": false, + "repeat": { + "mode": "variable", + "value": "server" + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 8, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-4" + }, + "repeat": { + "mode": "variable", + "value": "region", + "direction": "h", + "maxPerRow": 4 + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 0, + "width": 16, + "height": 6, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + } + ] + } + } + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "title": "Collapsed Row", + "collapse": true, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "panels" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-1h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Panel Conversions Test Dashboard", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v0alpha1.json new file mode 100644 index 00000000000..e4a5b7b74eb --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v0alpha1.json @@ -0,0 +1,360 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "variable-conversions-test", + "labels": { + "test": "variable-conversions" + } + }, + "spec": { + "annotations": { + "list": [] + }, + "description": "Testing all variable types and transformations", + "links": [], + "panels": [], + "tags": [ + "test", + "variables" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus-uid" + }, + "description": "Select a data source", + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "datasource_var", + "options": [ + { + "selected": true, + "text": "Prometheus", + "value": "prometheus-uid" + }, + { + "selected": false, + "text": "InfluxDB", + "value": "influx-uid" + } + ], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All", + "localhost:9090" + ], + "value": [ + "$__all", + "localhost:9090" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "definition": "label_values(up, instance)", + "description": "Available instances", + "hide": 1, + "includeAll": true, + "label": "Instance", + "multi": true, + "name": "query_var", + "options": [ + { + "selected": false, + "text": "All", + "value": "$__all" + }, + { + "selected": true, + "text": "localhost:9090", + "value": "localhost:9090" + }, + { + "selected": false, + "text": "localhost:9091", + "value": "localhost:9091" + } + ], + "query": "label_values(up, instance)", + "refresh": 2, + "regex": "/.*9090.*/", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": { + "selected": false, + "text": "prod", + "value": "prod" + }, + "description": "Deployment environment", + "hide": 0, + "includeAll": true, + "label": "Environment", + "multi": false, + "name": "custom_var", + "options": [ + { + "selected": true, + "text": "prod", + "value": "prod" + }, + { + "selected": false, + "text": "staging", + "value": "staging" + }, + { + "selected": false, + "text": "dev", + "value": "dev" + } + ], + "query": "prod,staging,dev", + "skipUrlSync": false, + "type": "custom" + }, + { + "allowCustomValue": true, + "baseFilters": [ + { + "key": "environment", + "operator": "=", + "value": "production" + } + ], + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "defaultKeys": [ + { + "text": "job", + "value": "job" + }, + { + "text": "instance", + "value": "instance" + } + ], + "description": "Add filters", + "filters": [ + { + "key": "job", + "operator": "=", + "value": "prometheus" + } + ], + "hide": 0, + "label": "Filters", + "name": "adhoc_var", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": { + "selected": false, + "text": "my-application", + "value": "my-application" + }, + "description": "Application name constant", + "hide": 2, + "label": "App Name", + "name": "constant_var", + "query": "my-application", + "skipUrlSync": true, + "type": "constant" + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "selected": false, + "text": "5m", + "value": "5m" + }, + "description": "Time interval for queries", + "hide": 0, + "label": "Interval", + "name": "interval_var", + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": true, + "text": "5m", + "value": "5m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + } + ], + "query": "1m,5m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, + { + "current": { + "selected": false, + "text": "error", + "value": "error" + }, + "description": "Enter search term", + "hide": 0, + "label": "Search Term", + "name": "textbox_var", + "query": "default_search", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": [ + "job", + "instance" + ], + "value": [ + "job", + "instance" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "description": "Group results by field", + "hide": 0, + "label": "Group By", + "multi": true, + "name": "groupby_var", + "options": [ + { + "selected": true, + "text": "job", + "value": "job" + }, + { + "selected": true, + "text": "instance", + "value": "instance" + }, + { + "selected": false, + "text": "region", + "value": "region" + } + ], + "query": "", + "skipUrlSync": false, + "type": "groupby" + }, + { + "current": { + "selected": false, + "text": "legacy_value", + "value": "legacy_string_content" + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "description": "Variable with legacy string value", + "hide": 0, + "label": "Legacy Variable", + "name": "legacy_string_var", + "query": "up", + "refresh": 1, + "skipUrlSync": false, + "type": "query" + }, + { + "allValue": "*", + "current": { + "selected": true, + "text": [ + "All", + "host1", + "host2" + ], + "value": [ + "$__all", + "host1", + "host2" + ] + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "definition": "terms field:@host size:100", + "description": "Variable with complex query and all features", + "hide": 0, + "includeAll": true, + "label": "Complex Query Variable", + "multi": true, + "name": "complex_query_var", + "options": [], + "query": { + "bucketAggs": [ + { + "field": "@host", + "id": "2", + "type": "terms" + } + ], + "query": "*", + "size": 100 + }, + "refresh": 1, + "regex": "/host[0-9]+/", + "skipUrlSync": false, + "sort": 2, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": true + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "title": "Variable Conversions Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json new file mode 100644 index 00000000000..556c3774d34 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json @@ -0,0 +1,403 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "variable-conversions-test", + "labels": { + "test": "variable-conversions" + } + }, + "spec": { + "annotations": [], + "cursorSync": "Off", + "description": "Testing all variable types and transformations", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "variables" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Variable Conversions Test Dashboard", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource_var", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "Prometheus", + "value": "prometheus-uid" + }, + "options": [ + { + "selected": true, + "text": "Prometheus", + "value": "prometheus-uid" + }, + { + "selected": false, + "text": "InfluxDB", + "value": "influx-uid" + } + ], + "multi": false, + "includeAll": false, + "label": "Data Source", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Select a data source", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var", + "current": { + "text": [ + "All", + "localhost:9090" + ], + "value": [ + "$__all", + "localhost:9090" + ] + }, + "label": "Instance", + "hide": "hideLabel", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "description": "Available instances", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "/.*9090.*/", + "sort": "alphabeticalAsc", + "definition": "label_values(up, instance)", + "options": [ + { + "selected": false, + "text": "All", + "value": "$__all" + }, + { + "selected": true, + "text": "localhost:9090", + "value": "localhost:9090" + }, + { + "selected": false, + "text": "localhost:9091", + "value": "localhost:9091" + } + ], + "multi": true, + "includeAll": true, + "allValue": ".*", + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_var", + "query": "prod,staging,dev", + "current": { + "text": "prod", + "value": "prod" + }, + "options": [ + { + "selected": true, + "text": "prod", + "value": "prod" + }, + { + "selected": false, + "text": "staging", + "value": "staging" + }, + { + "selected": false, + "text": "dev", + "value": "dev" + } + ], + "multi": false, + "includeAll": true, + "label": "Environment", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Deployment environment", + "allowCustomValue": true + } + }, + { + "kind": "AdhocVariable", + "spec": { + "name": "adhoc_var", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "baseFilters": [ + { + "key": "environment", + "operator": "=", + "value": "production" + } + ], + "filters": [ + { + "key": "job", + "operator": "=", + "value": "prometheus" + } + ], + "defaultKeys": [ + { + "text": "job", + "value": "job" + }, + { + "text": "instance", + "value": "instance" + } + ], + "label": "Filters", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Add filters", + "allowCustomValue": true + } + }, + { + "kind": "ConstantVariable", + "spec": { + "name": "constant_var", + "query": "my-application", + "current": { + "text": "my-application", + "value": "my-application" + }, + "label": "App Name", + "hide": "hideVariable", + "skipUrlSync": true, + "description": "Application name constant" + } + }, + { + "kind": "IntervalVariable", + "spec": { + "name": "interval_var", + "query": "1m,5m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "current": { + "text": "5m", + "value": "5m" + }, + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": true, + "text": "5m", + "value": "5m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + } + ], + "auto": true, + "auto_min": "10s", + "auto_count": 30, + "refresh": "onTimeRangeChanged", + "label": "Interval", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Time interval for queries" + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "textbox_var", + "current": { + "text": "error", + "value": "error" + }, + "query": "default_search", + "label": "Search Term", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Enter search term" + } + }, + { + "kind": "GroupByVariable", + "spec": { + "name": "groupby_var", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "current": { + "text": [ + "job", + "instance" + ], + "value": [ + "job", + "instance" + ] + }, + "options": [ + { + "selected": true, + "text": "job", + "value": "job" + }, + { + "selected": true, + "text": "instance", + "value": "instance" + }, + { + "selected": false, + "text": "region", + "value": "region" + } + ], + "multi": true, + "label": "Group By", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Group results by field" + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "legacy_string_var", + "current": { + "text": "legacy_value", + "value": "legacy_string_content" + }, + "label": "Legacy Variable", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "description": "Variable with legacy string value", + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "__legacyStringValue": "up" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "complex_query_var", + "current": { + "text": [ + "All", + "host1", + "host2" + ], + "value": [ + "$__all", + "host1", + "host2" + ] + }, + "label": "Complex Query Variable", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "description": "Variable with complex query and all features", + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "query": { + "kind": "DataQuery", + "spec": { + "bucketAggs": [ + { + "field": "@host", + "id": "2", + "type": "terms" + } + ], + "query": "*", + "size": 100 + } + }, + "regex": "/host[0-9]+/", + "sort": "alphabeticalDesc", + "definition": "terms field:@host size:100", + "options": [], + "multi": true, + "includeAll": true, + "allValue": "*", + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json new file mode 100644 index 00000000000..d35e57fa601 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json @@ -0,0 +1,406 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "variable-conversions-test", + "labels": { + "test": "variable-conversions" + } + }, + "spec": { + "annotations": [], + "cursorSync": "Off", + "description": "Testing all variable types and transformations", + "editable": true, + "elements": {}, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "variables" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Variable Conversions Test Dashboard", + "variables": [ + { + "kind": "DatasourceVariable", + "spec": { + "name": "datasource_var", + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "current": { + "text": "Prometheus", + "value": "prometheus-uid" + }, + "options": [ + { + "selected": true, + "text": "Prometheus", + "value": "prometheus-uid" + }, + { + "selected": false, + "text": "InfluxDB", + "value": "influx-uid" + } + ], + "multi": false, + "includeAll": false, + "label": "Data Source", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Select a data source", + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "query_var", + "current": { + "text": [ + "All", + "localhost:9090" + ], + "value": [ + "$__all", + "localhost:9090" + ] + }, + "label": "Instance", + "hide": "hideLabel", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + "description": "Available instances", + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "__legacyStringValue": "label_values(up, instance)" + } + }, + "regex": "/.*9090.*/", + "sort": "alphabeticalAsc", + "definition": "label_values(up, instance)", + "options": [ + { + "selected": false, + "text": "All", + "value": "$__all" + }, + { + "selected": true, + "text": "localhost:9090", + "value": "localhost:9090" + }, + { + "selected": false, + "text": "localhost:9091", + "value": "localhost:9091" + } + ], + "multi": true, + "includeAll": true, + "allValue": ".*", + "allowCustomValue": true + } + }, + { + "kind": "CustomVariable", + "spec": { + "name": "custom_var", + "query": "prod,staging,dev", + "current": { + "text": "prod", + "value": "prod" + }, + "options": [ + { + "selected": true, + "text": "prod", + "value": "prod" + }, + { + "selected": false, + "text": "staging", + "value": "staging" + }, + { + "selected": false, + "text": "dev", + "value": "dev" + } + ], + "multi": false, + "includeAll": true, + "label": "Environment", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Deployment environment", + "allowCustomValue": true + } + }, + { + "kind": "AdhocVariable", + "group": "prometheus", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "name": "adhoc_var", + "baseFilters": [ + { + "key": "environment", + "operator": "=", + "value": "production" + } + ], + "filters": [ + { + "key": "job", + "operator": "=", + "value": "prometheus" + } + ], + "defaultKeys": [ + { + "text": "job", + "value": "job" + }, + { + "text": "instance", + "value": "instance" + } + ], + "label": "Filters", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Add filters", + "allowCustomValue": true + } + }, + { + "kind": "ConstantVariable", + "spec": { + "name": "constant_var", + "query": "my-application", + "current": { + "text": "my-application", + "value": "my-application" + }, + "label": "App Name", + "hide": "hideVariable", + "skipUrlSync": true, + "description": "Application name constant" + } + }, + { + "kind": "IntervalVariable", + "spec": { + "name": "interval_var", + "query": "1m,5m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "current": { + "text": "5m", + "value": "5m" + }, + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": true, + "text": "5m", + "value": "5m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + } + ], + "auto": true, + "auto_min": "10s", + "auto_count": 30, + "refresh": "onTimeRangeChanged", + "label": "Interval", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Time interval for queries" + } + }, + { + "kind": "TextVariable", + "spec": { + "name": "textbox_var", + "current": { + "text": "error", + "value": "error" + }, + "query": "default_search", + "label": "Search Term", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Enter search term" + } + }, + { + "kind": "GroupByVariable", + "group": "prometheus", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "name": "groupby_var", + "current": { + "text": [ + "job", + "instance" + ], + "value": [ + "job", + "instance" + ] + }, + "options": [ + { + "selected": true, + "text": "job", + "value": "job" + }, + { + "selected": true, + "text": "instance", + "value": "instance" + }, + { + "selected": false, + "text": "region", + "value": "region" + } + ], + "multi": true, + "label": "Group By", + "hide": "dontHide", + "skipUrlSync": false, + "description": "Group results by field" + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "legacy_string_var", + "current": { + "text": "legacy_value", + "value": "legacy_string_content" + }, + "label": "Legacy Variable", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "description": "Variable with legacy string value", + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "existing-ref-uid" + }, + "spec": { + "__legacyStringValue": "up" + } + }, + "regex": "", + "sort": "disabled", + "options": [], + "multi": false, + "includeAll": false, + "allowCustomValue": true + } + }, + { + "kind": "QueryVariable", + "spec": { + "name": "complex_query_var", + "current": { + "text": [ + "All", + "host1", + "host2" + ], + "value": [ + "$__all", + "host1", + "host2" + ] + }, + "label": "Complex Query Variable", + "hide": "dontHide", + "refresh": "onDashboardLoad", + "skipUrlSync": false, + "description": "Variable with complex query and all features", + "query": { + "kind": "DataQuery", + "group": "elasticsearch", + "version": "v0", + "datasource": { + "name": "existing-target-uid" + }, + "spec": { + "bucketAggs": [ + { + "field": "@host", + "id": "2", + "type": "terms" + } + ], + "query": "*", + "size": 100 + } + }, + "regex": "/host[0-9]+/", + "sort": "alphabeticalDesc", + "definition": "terms field:@host size:100", + "options": [], + "multi": true, + "includeAll": true, + "allValue": "*", + "allowCustomValue": true + } + } + ] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json index a7fa8532074..6d044300534 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json @@ -79,7 +79,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "datasource": { "name": "gdev-prometheus" @@ -399,7 +399,7 @@ "auto": true, "auto_min": "10s", "auto_count": 30, - "refresh": "load", + "refresh": "onTimeRangeChanged", "label": "Interval", "hide": "dontHide", "skipUrlSync": false, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json index e0efe19ed2b..7e8f0151ebb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json @@ -58,7 +58,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "spec": { "lines": 10, @@ -103,7 +103,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "spec": { "expr": "{action=\"add_client\"}", @@ -213,7 +213,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "datasource": { "name": "gdev-testdata" @@ -324,7 +324,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "spec": { "scenarioId": "random_walk", @@ -403,7 +403,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "spec": { "disableTextWrap": false, @@ -518,7 +518,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "datasource": { "name": "gdev-prometheus" @@ -636,7 +636,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "datasource": { "name": "gdev-prometheus" @@ -661,7 +661,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "datasource": { "name": "gdev-testdata" @@ -769,7 +769,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "spec": { "disableTextWrap": false, @@ -791,7 +791,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "spec": {} }, @@ -984,7 +984,7 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "datasource": { "name": "gdev-prometheus" @@ -1014,7 +1014,7 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "spec": { "csv": "1,2,3,4", @@ -1042,7 +1042,7 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "spec": {} }, @@ -1067,7 +1067,7 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", "spec": { "expr": "up" diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json index a7f48d67563..ae0dc06ab00 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json @@ -24,7 +24,7 @@ "spec": { "query": { "kind": "DataQuery", - "group": "grafana-testdata-datasource", + "group": "", "version": "v0", "datasource": { "name": "gdev-testdata" diff --git a/apps/dashboard/pkg/migration/conversion/v0.go b/apps/dashboard/pkg/migration/conversion/v0.go index 9eb42167072..81ea2db5e60 100644 --- a/apps/dashboard/pkg/migration/conversion/v0.go +++ b/apps/dashboard/pkg/migration/conversion/v0.go @@ -1,10 +1,6 @@ package conversion import ( - "context" - - "github.com/grafana/authlib/types" - "github.com/grafana/grafana/pkg/apimachinery/identity" "k8s.io/apimachinery/pkg/conversion" "k8s.io/utils/ptr" @@ -12,91 +8,104 @@ 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/apps/dashboard/pkg/migration" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" - "k8s.io/apiserver/pkg/endpoints/request" ) -func Convert_V0_to_V1(in *dashv0.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - - out.Spec.Object = in.Spec.Object - - out.Status = dashv1.DashboardStatus{ - Conversion: &dashv1.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv0.VERSION), - }, - } - - // the scope passed into this function is used in k8s apimachinery for migrations, but we also need the context - // to have what grafana expects in the request context, so that we can retrieve datasources for migrating - // some of the old dashboard schemas (these migrations used to be run in the frontend) - ctx := request.WithNamespace(context.Background(), in.GetNamespace()) - nsInfo, err := types.ParseNamespace(in.GetNamespace()) - if err != nil { - out.Status.Conversion.Failed = true - out.Status.Conversion.Error = ptr.To(err.Error()) - return schemaversion.NewMigrationError(err.Error(), schemaversion.GetSchemaVersion(in.Spec.Object), schemaversion.LATEST_VERSION, "Convert_V0_to_V1") - } - - // a background service identity is used here because the user who is reading the specific dashboard - // may not have access to all the datasources in the dashboard, but the migration still needs to take place - // in order to be able to convert between k8s versions (so that we have a guaranteed structure to convert between) - ctx, _ = identity.WithServiceIdentity(ctx, nsInfo.OrgID) - - if err := migration.Migrate(ctx, out.Spec.Object, schemaversion.LATEST_VERSION); err != nil { - out.Status.Conversion.Failed = true - out.Status.Conversion.Error = ptr.To(err.Error()) - return schemaversion.NewMigrationError(err.Error(), schemaversion.GetSchemaVersion(in.Spec.Object), schemaversion.LATEST_VERSION, "Convert_V0_to_V1") - } - - return nil -} - -func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - - // TODO (@radiohead): implement V0 to V2 conversion - // This is the bare minimum conversion that is needed to make the dashboard servable. - - if v, ok := in.Spec.Object["title"]; ok { - if title, ok := v.(string); ok { - out.Spec.Title = title +func Convert_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { + if err := ConvertDashboard_V0_to_V1beta1(in, out, scope); err != nil { + out.Status = dashv1.DashboardStatus{ + Conversion: &dashv1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, } + return err + } + return nil +} + +func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope, dsInfoProvider schemaversion.DataSourceInfoProvider) error { + v1beta1 := &dashv1.Dashboard{} + if err := ConvertDashboard_V0_to_V1beta1(in, v1beta1, scope); err != nil { + out.Status = dashv2alpha1.DashboardStatus{ + Conversion: &dashv2alpha1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + // Don't return error - just set status (matches test expectations) + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + } + } + return nil } - // We need to make sure the layout is set to some value, otherwise the JSON marshaling will fail. - out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ - GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ - Kind: "GridLayout", - Spec: dashv2alpha1.DashboardGridLayoutSpec{}, - }, - } - - out.Status = dashv2alpha1.DashboardStatus{ - Conversion: &dashv2alpha1.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv0.VERSION), - Failed: true, - Error: ptr.To("backend conversion not yet implemented"), - Source: in, - }, + if err := ConvertDashboard_V1beta1_to_V2alpha1(v1beta1, out, scope, dsInfoProvider); err != nil { + out.Status = dashv2alpha1.DashboardStatus{ + Conversion: &dashv2alpha1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + // Don't return error - just set status (matches test expectations) + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + } + } + return nil } return nil } -func Convert_V0_to_V2beta1(in *dashv0.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta +func Convert_V0_to_V2beta1(in *dashv0.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope, dsInfoProvider schemaversion.DataSourceInfoProvider) error { + v1beta1 := &dashv1.Dashboard{} + if err := ConvertDashboard_V0_to_V1beta1(in, v1beta1, scope); err != nil { + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + return err + } - // TODO: implement V0 to v2beta1 conversion + v2alpha1 := &dashv2alpha1.Dashboard{} + if err := ConvertDashboard_V1beta1_to_V2alpha1(v1beta1, v2alpha1, scope, dsInfoProvider); err != nil { + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + return err + } - out.Status = dashv2beta1.DashboardStatus{ - Conversion: &dashv2beta1.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv0.VERSION), - Failed: true, - Error: ptr.To("backend conversion not yet implemented"), - Source: in, - }, + if err := ConvertDashboard_V2alpha1_to_V2beta1(v2alpha1, out, scope); err != nil { + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + return err } return nil diff --git a/apps/dashboard/pkg/migration/conversion/v0_test.go b/apps/dashboard/pkg/migration/conversion/v0_test.go new file mode 100644 index 00000000000..40f118dc9f2 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/v0_test.go @@ -0,0 +1,392 @@ +package conversion + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + 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/apps/dashboard/pkg/migration" + migrationtestutil "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// TestV0ConversionErrorHandling tests that v0 conversion functions properly return errors and set status +func TestV0ConversionErrorHandling(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) + + tests := []struct { + name string + source *dashv0.Dashboard + target interface{} + expectError bool + expectStatusSet bool + checkStatus func(t *testing.T, target interface{}) + }{ + { + name: "Convert_V0_to_V1beta1 returns error on migration failure", + source: &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + }, + target: &dashv1.Dashboard{}, + expectError: true, + expectStatusSet: true, + checkStatus: func(t *testing.T, target interface{}) { + out := target.(*dashv1.Dashboard) + require.NotNil(t, out.Status.Conversion) + require.True(t, out.Status.Conversion.Failed) + require.NotNil(t, out.Status.Conversion.Error) + }, + }, + { + name: "Convert_V0_to_V2alpha1 sets status on first step migration failure", + source: &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + }, + target: &dashv2alpha1.Dashboard{}, + expectError: false, // Convert_V0_to_V2alpha1 doesn't return error, just sets status + expectStatusSet: true, + checkStatus: func(t *testing.T, target interface{}) { + out := target.(*dashv2alpha1.Dashboard) + // Status should be set when first step fails + require.NotNil(t, out.Status.Conversion) + require.True(t, out.Status.Conversion.Failed) + require.NotNil(t, out.Status.Conversion.Error) + }, + }, + { + name: "Convert_V0_to_V2beta1 returns error on first step migration failure", + source: &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + }, + target: &dashv2beta1.Dashboard{}, + expectError: true, + expectStatusSet: true, + checkStatus: func(t *testing.T, target interface{}) { + out := target.(*dashv2beta1.Dashboard) + require.NotNil(t, out.Status.Conversion) + require.True(t, out.Status.Conversion.Failed) + require.NotNil(t, out.Status.Conversion.Error) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var err error + switch target := tt.target.(type) { + case *dashv1.Dashboard: + err = Convert_V0_to_V1beta1(tt.source, target, nil) + case *dashv2alpha1.Dashboard: + err = Convert_V0_to_V2alpha1(tt.source, target, nil, dsProvider) + case *dashv2beta1.Dashboard: + err = Convert_V0_to_V2beta1(tt.source, target, nil, dsProvider) + default: + t.Fatalf("unexpected target type: %T", target) + } + + if tt.expectError { + require.Error(t, err, "expected conversion to return error") + } + + if tt.expectStatusSet { + tt.checkStatus(t, tt.target) + } + }) + } +} + +// TestV0ConversionErrorPropagation tests that errors from atomic functions are properly propagated +func TestV0ConversionErrorPropagation(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) + + t.Run("ConvertDashboard_V0_to_V1beta1 returns error on migration failure", func(t *testing.T) { + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + } + target := &dashv1.Dashboard{} + + err := ConvertDashboard_V0_to_V1beta1(source, target, nil) + + require.Error(t, err, "expected error on migration failure") + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + }) + + t.Run("Convert_V0_to_V1beta1 propagates error from ConvertDashboard_V0_to_V1beta1", func(t *testing.T) { + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + } + target := &dashv1.Dashboard{} + + err := Convert_V0_to_V1beta1(source, target, nil) + + require.Error(t, err, "expected error to be returned") + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + }) + + t.Run("Convert_V0_to_V2beta1 returns error on first step migration failure", func(t *testing.T) { + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V0_to_V2beta1(source, target, nil, dsProvider) + + require.Error(t, err, "expected error to be returned on first step failure") + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + }) +} + +// TestV0ConversionSuccessPaths tests that successful conversion paths are covered +func TestV0ConversionSuccessPaths(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) + + t.Run("Convert_V0_to_V1beta1 success path returns nil", func(t *testing.T) { + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, // Latest schema version - no migration needed + }, + }, + } + target := &dashv1.Dashboard{} + + err := Convert_V0_to_V1beta1(source, target, nil) + + require.NoError(t, err, "expected successful conversion") + require.NotNil(t, target.Status.Conversion) + require.False(t, target.Status.Conversion.Failed) + }) + + t.Run("Convert_V0_to_V2alpha1 success path returns nil", func(t *testing.T) { + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, // Latest schema version - no migration needed + }, + }, + } + target := &dashv2alpha1.Dashboard{} + + err := Convert_V0_to_V2alpha1(source, target, nil, dsProvider) + + require.NoError(t, err, "expected successful conversion") + // Layout should be set even on success + require.NotNil(t, target.Spec.Layout.GridLayoutKind) + }) + + t.Run("Convert_V0_to_V2beta1 success path returns nil", func(t *testing.T) { + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, // Latest schema version - no migration needed + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V0_to_V2beta1(source, target, nil, dsProvider) + + require.NoError(t, err, "expected successful conversion") + }) +} + +// TestV0ConversionSecondStepErrors tests error handling in second step of multi-step conversions +func TestV0ConversionSecondStepErrors(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) + + t.Run("Convert_V0_to_V2alpha1 sets status on first step error", func(t *testing.T) { + // Create a dashboard that will fail v0->v1beta1 conversion + // Use schemaVersion 0 which will cause migration to fail + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version that will cause migration to fail + }, + }, + } + target := &dashv2alpha1.Dashboard{} + + err := Convert_V0_to_V2alpha1(source, target, nil, dsProvider) + + // Convert_V0_to_V2alpha1 doesn't return error, just sets status + require.NoError(t, err, "Convert_V0_to_V2alpha1 doesn't return error") + // Status should be set when first step fails + require.NotNil(t, target.Status.Conversion, "Status should be set on first step error") + require.True(t, target.Status.Conversion.Failed, "Failed should be true") + require.NotNil(t, target.Status.Conversion.Error, "Error should be set") + require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) + }) + + t.Run("Convert_V0_to_V2alpha1 sets status on second step error", func(t *testing.T) { + // Create a dashboard that will pass v0->v1beta1 but fail v1beta1->v2alpha1 + // We need to create invalid JSON structure that will cause JSON marshaling to fail + // or create a dashboard with invalid structure that causes transformation to fail + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, // Valid schema version that will pass first step + "dashboard": map[string]interface{}{ + "title": "test", + // Create invalid structure that will cause JSON marshaling to fail + // by using a channel which cannot be marshaled + "invalidField": make(chan int), // This will cause JSON marshal to fail + }, + }, + }, + } + target := &dashv2alpha1.Dashboard{} + + err := Convert_V0_to_V2alpha1(source, target, nil, dsProvider) + + // Convert_V0_to_V2alpha1 doesn't return error, just sets status + require.NoError(t, err, "Convert_V0_to_V2alpha1 doesn't return error") + // If second step fails, status should be set + // Note: The error might occur in first step if JSON marshal fails early, + // but we're testing that the error handling path exists + if target.Status.Conversion != nil && target.Status.Conversion.Failed { + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) + } + }) + + t.Run("Convert_V0_to_V2beta1 returns error on second step failure", func(t *testing.T) { + // Create a scenario where first step succeeds but second step fails + // This is harder to trigger, but we can test the code path exists + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 25, // Valid schema version that will pass first step + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V0_to_V2beta1(source, target, nil, dsProvider) + + // May or may not error depending on dashboard content + // But if it does error on second step, status should be set + if err != nil { + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + } + }) + + t.Run("Convert_V0_to_V2beta1 returns error on third step failure", func(t *testing.T) { + // Create a scenario where first two steps succeed but third step fails + source := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 25, // Valid schema version that will pass first two steps + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V0_to_V2beta1(source, target, nil, dsProvider) + + // May or may not error depending on dashboard content + // But if it does error on third step, status should be set + if err != nil { + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + } + }) +} diff --git a/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go b/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go new file mode 100644 index 00000000000..3e78efdcb29 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go @@ -0,0 +1,81 @@ +package conversion + +import ( + "context" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/utils/ptr" + + "github.com/grafana/authlib/types" + dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + "github.com/grafana/grafana/apps/dashboard/pkg/migration" + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +// prepareV0ConversionContext sets up the context with namespace and service identity +// for v0 dashboard conversions. This context is needed to retrieve datasources for migrating +// old dashboard schemas (these migrations used to be run in the frontend). +// A background service identity is used because the user who is reading the specific dashboard +// may not have access to all the datasources in the dashboard, but the migration still needs to take place +// in order to be able to convert between k8s versions (so that we have a guaranteed structure to convert between). +func prepareV0ConversionContext(in *dashv0.Dashboard) (context.Context, *types.NamespaceInfo, error) { + ctx := request.WithNamespace(context.Background(), in.GetNamespace()) + nsInfo, err := types.ParseNamespace(in.GetNamespace()) + if err != nil { + return nil, nil, err + } + + ctx, _ = identity.WithServiceIdentity(ctx, nsInfo.OrgID) + return ctx, &nsInfo, nil +} + +// migrateV0Dashboard migrates a v0 dashboard object to the target schema version. +// This is used to ensure the dashboard structure is consistent before converting between k8s API versions. +func migrateV0Dashboard(ctx context.Context, dashboardObject map[string]interface{}, targetVersion int) error { + if ctx == nil { + return schemaversion.NewMigrationError("context is nil", schemaversion.GetSchemaVersion(dashboardObject), targetVersion, "migrateV0Dashboard") + } + return migration.Migrate(ctx, dashboardObject, targetVersion) +} + +// ConvertDashboard_V0_to_V1beta1 converts a v0alpha1 dashboard to v1beta1 format. +// This is an atomic single-step conversion that: +// 1. Migrates the dashboard to the latest schema version +// 2. Transforms the migrated dashboard to v1beta1 format +// +// the scope passed into this function is used in k8s apimachinery for migrations, but we also need the context +// to have what grafana expects in the request context, so that we can retrieve datasources for migrating +// some of the old dashboard schemas (these migrations used to be run in the frontend) +// +// a background service identity is used here because the user who is reading the specific dashboard +// may not have access to all the datasources in the dashboard, but the migration still needs to take place +// in order to be able to convert between k8s versions (so that we have a guaranteed structure to convert between) +func ConvertDashboard_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + + out.Spec.Object = in.Spec.Object + + out.Status = dashv1.DashboardStatus{ + Conversion: &dashv1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + }, + } + + ctx, _, err := prepareV0ConversionContext(in) + if err != nil { + out.Status.Conversion.Failed = true + out.Status.Conversion.Error = ptr.To(err.Error()) + return schemaversion.NewMigrationError(err.Error(), schemaversion.GetSchemaVersion(in.Spec.Object), schemaversion.LATEST_VERSION, "Convert_V0_to_V1") + } + + if err := migrateV0Dashboard(ctx, out.Spec.Object, schemaversion.LATEST_VERSION); err != nil { + out.Status.Conversion.Failed = true + out.Status.Conversion.Error = ptr.To(err.Error()) + return schemaversion.NewMigrationError(err.Error(), schemaversion.GetSchemaVersion(in.Spec.Object), schemaversion.LATEST_VERSION, "Convert_V0_to_V1") + } + + return nil +} diff --git a/apps/dashboard/pkg/migration/conversion/v1.go b/apps/dashboard/pkg/migration/conversion/v1.go index 86b3827af27..107d3010a9a 100644 --- a/apps/dashboard/pkg/migration/conversion/v1.go +++ b/apps/dashboard/pkg/migration/conversion/v1.go @@ -8,9 +8,10 @@ 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/apps/dashboard/pkg/migration/schemaversion" ) -func Convert_V1_to_V0(in *dashv1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope) error { +func Convert_V1beta1_to_V0(in *dashv1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Spec.Object = in.Spec.Object @@ -24,50 +25,63 @@ func Convert_V1_to_V0(in *dashv1.Dashboard, out *dashv0.Dashboard, scope convers return nil } -func Convert_V1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - - // TODO (@radiohead): implement V1 to V2 conversion - // This is the bare minimum conversion that is needed to make the dashboard servable. - - if v, ok := in.Spec.Object["title"]; ok { - if title, ok := v.(string); ok { - out.Spec.Title = title +func Convert_V1beta1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope, dsInfoProvider schemaversion.DataSourceInfoProvider) error { + if err := ConvertDashboard_V1beta1_to_V2alpha1(in, out, scope, dsInfoProvider); err != nil { + out.Status = dashv2alpha1.DashboardStatus{ + Conversion: &dashv2alpha1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, } + // Don't return error - just set status (matches test expectations and V0 pattern for Convert_V0_to_V2alpha1) + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + } + } + return nil } // We need to make sure the layout is set to some value, otherwise the JSON marshaling will fail. - out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ - GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ - Kind: "GridLayout", - Spec: dashv2alpha1.DashboardGridLayoutSpec{}, - }, - } - - out.Status = dashv2alpha1.DashboardStatus{ - Conversion: &dashv2alpha1.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv1.VERSION), - Failed: true, - Error: ptr.To("backend conversion not yet implemented"), - Source: in, - }, + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + } } return nil } -func Convert_V1_to_V2beta1(in *dashv1.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta +func Convert_V1beta1_to_V2beta1(in *dashv1.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope, dsInfoProvider schemaversion.DataSourceInfoProvider) error { + v2alpha1 := &dashv2alpha1.Dashboard{} + if err := ConvertDashboard_V1beta1_to_V2alpha1(in, v2alpha1, scope, dsInfoProvider); err != nil { + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + return err + } - // TODO: implement V1 to v2beta1 conversion - - out.Status = dashv2beta1.DashboardStatus{ - Conversion: &dashv2beta1.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv1.VERSION), - Failed: true, - Error: ptr.To("backend conversion not yet implemented"), - Source: in, - }, + if err := ConvertDashboard_V2alpha1_to_V2beta1(v2alpha1, out, scope); err != nil { + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + }, + } + return err } return nil diff --git a/apps/dashboard/pkg/migration/conversion/v1_test.go b/apps/dashboard/pkg/migration/conversion/v1_test.go new file mode 100644 index 00000000000..c62d5e60d34 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/v1_test.go @@ -0,0 +1,127 @@ +package conversion + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + 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/apps/dashboard/pkg/migration" + migrationtestutil "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// TestV1ConversionErrorHandling tests that v1 conversion functions properly handle errors +func TestV1ConversionErrorHandling(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + migration.Initialize(dsProvider) + + t.Run("Convert_V1beta1_to_V2alpha1 sets status on conversion error", func(t *testing.T) { + // Create a dashboard that will cause conversion to fail + // We can use an invalid dashboard structure + source := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + // Missing required fields that might cause conversion to fail + }, + }, + } + target := &dashv2alpha1.Dashboard{} + + err := Convert_V1beta1_to_V2alpha1(source, target, nil, dsProvider) + + // Convert_V1beta1_to_V2alpha1 doesn't return error, just sets status + require.NoError(t, err, "Convert_V1beta1_to_V2alpha1 doesn't return error") + // Layout should always be set + require.NotNil(t, target.Spec.Layout.GridLayoutKind) + // If conversion failed, status should be set + if target.Status.Conversion != nil { + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + } + }) + + t.Run("Convert_V1beta1_to_V2beta1 returns error on first step failure", func(t *testing.T) { + source := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + // Missing required fields that might cause conversion to fail + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V1beta1_to_V2beta1(source, target, nil, dsProvider) + + // May or may not error depending on dashboard content + // But if it does error on first step, status should be set with correct StoredVersion + if err != nil { + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + require.Equal(t, dashv1.VERSION, *target.Status.Conversion.StoredVersion) + } + }) + + t.Run("Convert_V1beta1_to_V2beta1 returns error on second step failure", func(t *testing.T) { + // Create a dashboard that will pass first step but might fail second step + source := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 25, // Valid schema version + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V1beta1_to_V2beta1(source, target, nil, dsProvider) + + // May or may not error depending on dashboard content + // But if it does error on second step, status should be set with correct StoredVersion + if err != nil { + require.NotNil(t, target.Status.Conversion) + require.True(t, target.Status.Conversion.Failed) + require.NotNil(t, target.Status.Conversion.Error) + require.Equal(t, dashv1.VERSION, *target.Status.Conversion.StoredVersion) + } + }) + + t.Run("Convert_V1beta1_to_V2beta1 success path returns nil", func(t *testing.T) { + source := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 25, // Valid schema version + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V1beta1_to_V2beta1(source, target, nil, dsProvider) + + // Should succeed if dashboard is valid + if err == nil { + // Success path covered + require.NoError(t, err) + } + }) +} diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go new file mode 100644 index 00000000000..c514728509e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -0,0 +1,2415 @@ +package conversion + +import ( + "context" + "encoding/json" + "fmt" + "math" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apiserver/pkg/endpoints/request" + + "github.com/grafana/authlib/types" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + schemaversion "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +// getDefaultDatasourceType gets the default datasource type using the datasource provider +func getDefaultDatasourceType(ctx context.Context, provider schemaversion.DataSourceInfoProvider) string { + const defaultType = "grafana" + + if provider == nil { + return defaultType + } + + datasources := provider.GetDataSourceInfo(ctx) + for _, ds := range datasources { + if ds.Default { + return ds.Type + } + } + + return defaultType +} + +// getDatasourceTypeByUID gets the datasource type by UID using the datasource provider +func getDatasourceTypeByUID(ctx context.Context, uid string, provider schemaversion.DataSourceInfoProvider) string { + if uid == "" { + return getDefaultDatasourceType(ctx, provider) + } + + if provider == nil { + return "grafana" + } + + datasources := provider.GetDataSourceInfo(ctx) + for _, ds := range datasources { + if ds.UID == uid { + return ds.Type + } + } + + return getDefaultDatasourceType(ctx, provider) +} + +// prepareV1beta1ConversionContext sets up the context with namespace and service identity +// for v1beta1 dashboard conversions. This context is needed to retrieve datasources for +// converting dashboard datasource references. +// A background service identity is used because the user who is reading the specific dashboard +// may not have access to all the datasources in the dashboard, but the conversion still needs to take place +// in order to be able to convert between k8s versions. +func prepareV1beta1ConversionContext(in *dashv1.Dashboard, dsInfoProvider schemaversion.DataSourceInfoProvider) (context.Context, *types.NamespaceInfo, error) { + if dsInfoProvider == nil { + return nil, nil, fmt.Errorf("datasource provider not initialized") + } + + namespace := in.GetNamespace() + if namespace == "" { + return context.Background(), nil, nil + } + + // Try to parse namespace and set up tenant-aware context + ctx := request.WithNamespace(context.Background(), namespace) + nsInfo, err := types.ParseNamespace(namespace) + if err != nil { + return ctx, nil, nil + } + + ctx, _ = identity.WithServiceIdentity(ctx, nsInfo.OrgID) + + return ctx, &nsInfo, nil +} + +func ConvertDashboard_V1beta1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope, dsInfoProvider schemaversion.DataSourceInfoProvider) error { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2alpha1.APIVERSION + out.Kind = in.Kind + + // Prepare context with namespace and service identity + // The datasource provider is passed as a parameter (captured in closure when conversions are registered) + ctx, _, err := prepareV1beta1ConversionContext(in, dsInfoProvider) + if err != nil { + // If context preparation fails, return error to be handled by wrapper + // The wrapper will set status and handle gracefully + return fmt.Errorf("failed to prepare conversion context: %w", err) + } + + return convertDashboardSpec_V1beta1_to_V2alpha1(&in.Spec, &out.Spec, scope, ctx, dsInfoProvider) +} + +func convertDashboardSpec_V1beta1_to_V2alpha1(in *dashv1.DashboardSpec, out *dashv2alpha1.DashboardSpec, scope conversion.Scope, ctx context.Context, dsInfoProvider schemaversion.DataSourceInfoProvider) error { + // Parse the unstructured spec into a dashboard JSON structure + dashboardJSON, ok := in.Object["dashboard"] + if !ok { + // If no "dashboard" key, treat the entire object as the dashboard + dashboardJSON = in.Object + } + + // Convert to bytes and back to handle complex nested structures + dashBytes, err := json.Marshal(dashboardJSON) + if err != nil { + return fmt.Errorf("failed to marshal dashboard JSON: %w", err) + } + + var dashboard map[string]interface{} + if err := json.Unmarshal(dashBytes, &dashboard); err != nil { + return fmt.Errorf("failed to unmarshal dashboard JSON: %w", err) + } + + // Get defaults + timeSettingsDefaults := dashv2alpha1.NewDashboardTimeSettingsSpec() + dashboardDefaults := getDefaultDashboardV2Spec() + + // Transform basic fields + out.Title = schemaversion.GetStringValue(dashboard, "title") + out.Description = getStringPtr(dashboard, "description") + out.Tags = getStringSlice(dashboard, "tags") + out.CursorSync = transformCursorSyncToEnum(getIntField(dashboard, "graphTooltip", 0)) + out.Preload = getBoolField(dashboard, "preload", dashboardDefaults.Preload) + + // Add frontend-style default values + // Set default editable: true to match frontend behavior + defaultEditable := true + out.Editable = &defaultEditable + + // Set default liveNow: false to match frontend behavior + defaultLiveNow := false + out.LiveNow = &defaultLiveNow + + // Override with input values if they exist + if liveNow, exists := dashboard["liveNow"]; exists { + if liveNowBool, ok := liveNow.(bool); ok { + out.LiveNow = &liveNowBool + } + } + if editable, exists := dashboard["editable"]; exists { + if editableBool, ok := editable.(bool); ok { + out.Editable = &editableBool + } + } + if revision, exists := dashboard["revision"]; exists { + if revisionInt, ok := revision.(float64); ok { + revisionUint := uint16(revisionInt) + out.Revision = &revisionUint + } + } + + // Transform time settings + out.TimeSettings = transformTimeSettings(dashboard, timeSettingsDefaults) + + // Transform links + out.Links = transformLinks(dashboard) + + // Transform panels to elements and layout + elements, layout, err := transformPanelsToElementsAndLayout(ctx, dashboard, dsInfoProvider) + if err != nil { + return fmt.Errorf("failed to transform panels: %w", err) + } + out.Elements = elements + out.Layout = layout + + // Transform variables + variables, err := transformVariables(ctx, dashboard, dsInfoProvider) + if err != nil { + return fmt.Errorf("failed to transform variables: %w", err) + } + out.Variables = variables + + // Transform annotations + annotations, err := transformAnnotations(dashboard) + if err != nil { + return fmt.Errorf("failed to transform annotations: %w", err) + } + out.Annotations = annotations + + return nil +} + +func getStringPtr(m map[string]interface{}, key string) *string { + if val, ok := m[key]; ok { + if str, ok := val.(string); ok && str != "" { + return &str + } + } + return nil +} + +func getStringSlice(m map[string]interface{}, key string) []string { + if val, ok := m[key]; ok { + if slice, ok := val.([]interface{}); ok { + result := make([]string, 0, len(slice)) + for _, item := range slice { + if str, ok := item.(string); ok { + result = append(result, str) + } + } + return result + } + } + return []string{} +} + +func getBoolField(m map[string]interface{}, key string, defaultValue bool) bool { + if val, ok := m[key]; ok { + if b, ok := val.(bool); ok { + return b + } + } + return defaultValue +} + +// Helper function to create int64 pointer +func int64Ptr(i int64) *int64 { + return &i +} + +// isTemplateVariable checks if a string is a Grafana template variable reference +// Template variables can be in the form: $varname or ${varname} +func isTemplateVariable(s string) bool { + if s == "" { + return false + } + return strings.HasPrefix(s, "$") || strings.HasPrefix(s, "${") +} + +func getIntField(m map[string]interface{}, key string, defaultValue int) int { + if val, ok := m[key]; ok { + switch v := val.(type) { + case int: + return v + case float64: + return int(v) + case string: + if i, err := strconv.Atoi(v); err == nil { + return i + } + } + } + return defaultValue +} + +// Enum transformation functions +func transformCursorSyncToEnum(cursorSync int) dashv2alpha1.DashboardDashboardCursorSync { + switch cursorSync { + case 0: + return dashv2alpha1.DashboardDashboardCursorSyncOff + case 1: + return dashv2alpha1.DashboardDashboardCursorSyncCrosshair + case 2: + return dashv2alpha1.DashboardDashboardCursorSyncTooltip + default: + return dashv2alpha1.DashboardDashboardCursorSyncOff + } +} + +func getDefaultDashboardV2Spec() dashv2alpha1.DashboardSpec { + return dashv2alpha1.DashboardSpec{ + Preload: false, + } +} + +// Transform time settings +func transformTimeSettings(dashboard map[string]interface{}, defaults *dashv2alpha1.DashboardTimeSettingsSpec) dashv2alpha1.DashboardTimeSettingsSpec { + timeSettings := dashv2alpha1.DashboardTimeSettingsSpec{ + From: defaults.From, + To: defaults.To, + Timezone: defaults.Timezone, + AutoRefresh: defaults.AutoRefresh, + AutoRefreshIntervals: defaults.AutoRefreshIntervals, + FiscalYearStartMonth: defaults.FiscalYearStartMonth, + HideTimepicker: defaults.HideTimepicker, + // Don't initialize optional fields with defaults - only set them if present in input + } + + // Extract time range + if timeRange, ok := dashboard["time"].(map[string]interface{}); ok { + if from := schemaversion.GetStringValue(timeRange, "from"); from != "" { + timeSettings.From = from + } + if to := schemaversion.GetStringValue(timeRange, "to"); to != "" { + timeSettings.To = to + } + } + + // Extract other time-related fields + if timezone, exists := dashboard["timezone"]; exists { + if timezoneStr, ok := timezone.(string); ok { + timeSettings.Timezone = &timezoneStr + } + } + if refresh := schemaversion.GetStringValue(dashboard, "refresh"); refresh != "" { + timeSettings.AutoRefresh = refresh + } + + timeSettings.FiscalYearStartMonth = int64(getIntField(dashboard, "fiscalYearStartMonth", int(defaults.FiscalYearStartMonth))) + + if weekStart, exists := dashboard["weekStart"]; exists { + if weekStartStr, ok := weekStart.(string); ok && weekStartStr != "" { + weekStartEnum := dashv2alpha1.DashboardTimeSettingsSpecWeekStart(weekStartStr) + timeSettings.WeekStart = &weekStartEnum + } + } + + // Extract timepicker settings + if timepicker, ok := dashboard["timepicker"].(map[string]interface{}); ok { + if intervals, ok := timepicker["refresh_intervals"].([]interface{}); ok { + refreshIntervals := make([]string, 0, len(intervals)) + for _, interval := range intervals { + if str, ok := interval.(string); ok { + refreshIntervals = append(refreshIntervals, str) + } + } + if len(refreshIntervals) > 0 { + timeSettings.AutoRefreshIntervals = refreshIntervals + } + } + timeSettings.HideTimepicker = getBoolField(timepicker, "hidden", defaults.HideTimepicker) + if nowDelay, exists := timepicker["nowDelay"]; exists { + if nowDelayStr, ok := nowDelay.(string); ok && nowDelayStr != "" { + timeSettings.NowDelay = &nowDelayStr + } + } + + // Handle quick_ranges + if quickRanges, ok := timepicker["quick_ranges"].([]interface{}); ok { + ranges := make([]dashv2alpha1.DashboardTimeRangeOption, 0, len(quickRanges)) + for _, qr := range quickRanges { + if qrMap, ok := qr.(map[string]interface{}); ok { + quickRange := dashv2alpha1.DashboardTimeRangeOption{ + Display: schemaversion.GetStringValue(qrMap, "display"), + From: schemaversion.GetStringValue(qrMap, "from"), + To: schemaversion.GetStringValue(qrMap, "to"), + } + ranges = append(ranges, quickRange) + } + } + timeSettings.QuickRanges = ranges + } + } + + return timeSettings +} + +// Transform links +func transformLinks(dashboard map[string]interface{}) []dashv2alpha1.DashboardDashboardLink { + if links, ok := dashboard["links"].([]interface{}); ok { + result := make([]dashv2alpha1.DashboardDashboardLink, 0, len(links)) + for _, link := range links { + if linkMap, ok := link.(map[string]interface{}); ok { + // Required fields with defaults + dashLink := dashv2alpha1.DashboardDashboardLink{ + Title: schemaversion.GetStringValue(linkMap, "title"), + Type: dashv2alpha1.DashboardDashboardLinkType(schemaversion.GetStringValue(linkMap, "type", "link")), + Icon: schemaversion.GetStringValue(linkMap, "icon"), + Tooltip: schemaversion.GetStringValue(linkMap, "tooltip"), + Tags: getStringSlice(linkMap, "tags"), + AsDropdown: getBoolField(linkMap, "asDropdown", false), + TargetBlank: getBoolField(linkMap, "targetBlank", false), + IncludeVars: getBoolField(linkMap, "includeVars", false), + KeepTime: getBoolField(linkMap, "keepTime", false), + } + + // Optional field - only set if present + if url, exists := linkMap["url"]; exists { + if urlStr, ok := url.(string); ok { + dashLink.Url = &urlStr + } + } + + result = append(result, dashLink) + } + } + return result + } + return []dashv2alpha1.DashboardDashboardLink{} +} + +// Panel transformation constants +const GRID_ROW_HEIGHT = 1 + +func transformPanelsToElementsAndLayout(ctx context.Context, dashboard map[string]interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) (map[string]dashv2alpha1.DashboardElement, dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind, error) { + panels, ok := dashboard["panels"].([]interface{}) + if !ok { + // Return empty elements and default grid layout + elements := make(map[string]dashv2alpha1.DashboardElement) + layout := dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{}, + }, + }, + } + return elements, layout, nil + } + + // Check if any panels are row panels + hasRowPanels := false + for _, p := range panels { + if panelMap, ok := p.(map[string]interface{}); ok { + if schemaversion.GetStringValue(panelMap, "type") == "row" { + hasRowPanels = true + break + } + } + } + + if hasRowPanels { + return convertToRowsLayout(ctx, panels, dsInfoProvider) + } + + return convertToGridLayout(ctx, panels, dsInfoProvider) +} + +func convertToGridLayout(ctx context.Context, panels []interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) (map[string]dashv2alpha1.DashboardElement, dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind, error) { + elements := make(map[string]dashv2alpha1.DashboardElement) + items := make([]dashv2alpha1.DashboardGridLayoutItemKind, 0, len(panels)) + + for _, p := range panels { + panelMap, ok := p.(map[string]interface{}) + if !ok { + continue + } + + element, elementName, err := buildElement(ctx, panelMap, dsInfoProvider) + if err != nil { + continue // Skip invalid panels + } + + elements[elementName] = element + items = append(items, buildGridItemKind(panelMap, elementName, nil)) + } + + layout := dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: items, + }, + }, + } + + return elements, layout, nil +} + +func convertToRowsLayout(ctx context.Context, panels []interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) (map[string]dashv2alpha1.DashboardElement, dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind, error) { + elements := make(map[string]dashv2alpha1.DashboardElement) + rows := make([]dashv2alpha1.DashboardRowsLayoutRowKind, 0) + + var currentRow *dashv2alpha1.DashboardRowsLayoutRowKind + var legacyRowY int64 + + for _, p := range panels { + panelMap, ok := p.(map[string]interface{}) + if !ok { + continue + } + + if schemaversion.GetStringValue(panelMap, "type") == "row" { + // This is a row panel + if gridPos, ok := panelMap["gridPos"].(map[string]interface{}); ok { + legacyRowY = int64(getIntField(gridPos, "y", 0)) + } + + if currentRow != nil { + // If currentRow is a hidden-header row (panels before first explicit row), + // set its collapse to match the first explicit row's collapsed value + // This matches frontend behavior: collapse: panel.collapsed + if currentRow.Spec.HideHeader != nil && *currentRow.Spec.HideHeader { + rowCollapsed := getBoolField(panelMap, "collapsed", false) + currentRow.Spec.Collapse = &rowCollapsed + } + // Flush current row to layout + rows = append(rows, *currentRow) + } + + // Handle collapsed row panels + rowElements := make([]dashv2alpha1.DashboardGridLayoutItemKind, 0) + if collapsedPanels, ok := panelMap["panels"].([]interface{}); ok { + for _, panel := range collapsedPanels { + if collapsedPanelMap, ok := panel.(map[string]interface{}); ok { + element, name, err := buildElement(ctx, collapsedPanelMap, dsInfoProvider) + if err == nil { + elements[name] = element + rowElements = append(rowElements, buildGridItemKind(collapsedPanelMap, name, int64Ptr(yOffsetInRows(collapsedPanelMap, legacyRowY)))) + } + } + } + } + + currentRow = buildRowKind(panelMap, rowElements) + } else { + // Regular panel + element, elementName, err := buildElement(ctx, panelMap, dsInfoProvider) + if err != nil { + continue // Skip invalid panels + } + + elements[elementName] = element + + if currentRow != nil { + // Add to current row + if currentRow.Spec.Layout.GridLayoutKind != nil { + currentRow.Spec.Layout.GridLayoutKind.Spec.Items = append( + currentRow.Spec.Layout.GridLayoutKind.Spec.Items, + buildGridItemKind(panelMap, elementName, int64Ptr(yOffsetInRows(panelMap, legacyRowY))), + ) + } + } else { + // Create first row (hidden header) + // Since this row does not exist in V1, we simulate it being outside of the grid above the first panel + // The Y position does not matter for the rows layout, but it's used to calculate the position of the panels in the grid layout in the row. + legacyRowY = -1 + gridItems := []dashv2alpha1.DashboardGridLayoutItemKind{ + buildGridItemKind(panelMap, elementName, int64Ptr(0)), + } + + hideHeader := true + currentRow = &dashv2alpha1.DashboardRowsLayoutRowKind{ + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Collapse: &[]bool{false}[0], + Title: &[]string{""}[0], + HideHeader: &hideHeader, + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: gridItems, + }, + }, + }, + }, + } + } + } + } + + if currentRow != nil { + // Flush last row + rows = append(rows, *currentRow) + } + + layout := dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: rows, + }, + }, + } + + return elements, layout, nil +} + +func buildElement(ctx context.Context, panelMap map[string]interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) (dashv2alpha1.DashboardElement, string, error) { + panelID := getIntField(panelMap, "id", 0) + elementName := fmt.Sprintf("panel-%d", panelID) + + // Check if it's a library panel + if libraryPanel, ok := panelMap["libraryPanel"].(map[string]interface{}); ok { + libPanelKind := &dashv2alpha1.DashboardLibraryPanelKind{ + Kind: "LibraryPanel", + Spec: dashv2alpha1.DashboardLibraryPanelKindSpec{ + LibraryPanel: dashv2alpha1.DashboardLibraryPanelRef{ + Uid: schemaversion.GetStringValue(libraryPanel, "uid"), + Name: schemaversion.GetStringValue(libraryPanel, "name"), + }, + Id: float64(panelID), + Title: schemaversion.GetStringValue(panelMap, "title"), + }, + } + + element := dashv2alpha1.DashboardElement{ + LibraryPanelKind: libPanelKind, + } + + return element, elementName, nil + } + + // Regular panel + panelKind, err := buildPanelKind(ctx, panelMap, dsInfoProvider) + if err != nil { + return dashv2alpha1.DashboardElement{}, "", err + } + + element := dashv2alpha1.DashboardElement{ + PanelKind: panelKind, + } + + return element, elementName, nil +} + +func buildPanelKind(ctx context.Context, panelMap map[string]interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) (*dashv2alpha1.DashboardPanelKind, error) { + panelID := float64(getIntField(panelMap, "id", 0)) + + // Transform queries + queries := transformPanelQueries(ctx, panelMap, dsInfoProvider) + + // Transform transformations + transformations := transformPanelTransformations(panelMap) + + // Build query options + queryOptions := buildQueryOptions(panelMap) + + // Build data links + links := transformDataLinks(panelMap) + + // Build viz config + vizConfig := buildVizConfig(panelMap) + + panelKind := &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: panelID, + Title: schemaversion.GetStringValue(panelMap, "title"), + Description: schemaversion.GetStringValue(panelMap, "description"), + Links: links, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Kind: "QueryGroup", + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: queries, + Transformations: transformations, + QueryOptions: queryOptions, + }, + }, + VizConfig: vizConfig, + }, + } + + // Handle transparent panels + if transparent := getBoolField(panelMap, "transparent", false); transparent { + panelKind.Spec.Transparent = &transparent + } + + return panelKind, nil +} + +func buildGridItemKind(panelMap map[string]interface{}, elementName string, yOverride *int64) dashv2alpha1.DashboardGridLayoutItemKind { + // Default grid position (matches frontend PanelModel defaults: w=6, h=3) + x, y, width, height := int64(0), int64(0), int64(6), int64(3) + + if gridPos, ok := panelMap["gridPos"].(map[string]interface{}); ok { + x = int64(getIntField(gridPos, "x", 0)) + y = int64(getIntField(gridPos, "y", 0)) + width = int64(getIntField(gridPos, "w", 6)) + height = int64(getIntField(gridPos, "h", 3)) + } + + // Apply frontend-style grid position calculations + // Frontend recalculates positions based on row structure + if yOverride != nil { + y = *yOverride + } + + item := dashv2alpha1.DashboardGridLayoutItemKind{ + Kind: "GridLayoutItem", + Spec: dashv2alpha1.DashboardGridLayoutItemSpec{ + X: x, + Y: y, + Width: width, + Height: height, + Element: dashv2alpha1.DashboardElementReference{ + Kind: "ElementReference", + Name: elementName, + }, + }, + } + + // Handle repeat options + if repeat := schemaversion.GetStringValue(panelMap, "repeat"); repeat != "" { + repeatOptions := &dashv2alpha1.DashboardRepeatOptions{ + Mode: "variable", + Value: repeat, + } + + if repeatDirection := schemaversion.GetStringValue(panelMap, "repeatDirection"); repeatDirection != "" { + switch repeatDirection { + case "h": + direction := dashv2alpha1.DashboardRepeatOptionsDirectionH + repeatOptions.Direction = &direction + case "v": + direction := dashv2alpha1.DashboardRepeatOptionsDirectionV + repeatOptions.Direction = &direction + } + } + + if maxPerRow := getIntField(panelMap, "maxPerRow", 0); maxPerRow > 0 { + maxPerRowInt64 := int64(maxPerRow) + repeatOptions.MaxPerRow = &maxPerRowInt64 + } + + item.Spec.Repeat = repeatOptions + } + + return item +} + +func buildRowKind(rowPanelMap map[string]interface{}, elements []dashv2alpha1.DashboardGridLayoutItemKind) *dashv2alpha1.DashboardRowsLayoutRowKind { + collapsed := getBoolField(rowPanelMap, "collapsed", false) + title := schemaversion.GetStringValue(rowPanelMap, "title") + + row := &dashv2alpha1.DashboardRowsLayoutRowKind{ + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Collapse: &collapsed, + Title: &title, + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: elements, + }, + }, + }, + }, + } + + // Handle repeat options for rows + if repeat := schemaversion.GetStringValue(rowPanelMap, "repeat"); repeat != "" { + row.Spec.Repeat = &dashv2alpha1.DashboardRowRepeatOptions{ + Mode: "variable", + Value: repeat, + } + } + + return row +} + +func yOffsetInRows(panelMap map[string]interface{}, rowY int64) int64 { + if gridPos, ok := panelMap["gridPos"].(map[string]interface{}); ok { + panelY := int64(getIntField(gridPos, "y", 0)) + return panelY - rowY - GRID_ROW_HEIGHT + } + return 0 +} + +// LEGACY_STRING_VALUE_KEY is used for QueryVariableKind's query prop. Historically, queries +// could be defined as strings, but in schema V2 we've deprecated the string type and support +// only DataQuery objects. When a query is a simple string (legacy format), it's stored under +// this key in a map structure to preserve the value during migration from v1beta1 to v2alpha1. +const LEGACY_STRING_VALUE_KEY = "__legacyStringValue" + +// Variable enum transformation functions +func transformVariableHideToEnum(hide interface{}) dashv2alpha1.DashboardVariableHide { + switch v := hide.(type) { + case int: + switch v { + case 0: + return dashv2alpha1.DashboardVariableHideDontHide + case 1: + return dashv2alpha1.DashboardVariableHideHideLabel + case 2: + return dashv2alpha1.DashboardVariableHideHideVariable + default: + return dashv2alpha1.DashboardVariableHideDontHide + } + case float64: + return transformVariableHideToEnum(int(v)) + case string: + switch v { + case "dontHide", "": + return dashv2alpha1.DashboardVariableHideDontHide + case "hideLabel": + return dashv2alpha1.DashboardVariableHideHideLabel + case "hideVariable": + return dashv2alpha1.DashboardVariableHideHideVariable + default: + return dashv2alpha1.DashboardVariableHideDontHide + } + default: + return dashv2alpha1.DashboardVariableHideDontHide + } +} + +func transformVariableRefreshToEnum(refresh interface{}) dashv2alpha1.DashboardVariableRefresh { + switch v := refresh.(type) { + case int: + switch v { + case 0: + return dashv2alpha1.DashboardVariableRefreshNever + case 1: + return dashv2alpha1.DashboardVariableRefreshOnDashboardLoad + case 2: + return dashv2alpha1.DashboardVariableRefreshOnTimeRangeChanged + default: + return dashv2alpha1.DashboardVariableRefreshNever + } + case float64: + return transformVariableRefreshToEnum(int(v)) + case string: + switch v { + case "never", "": + return dashv2alpha1.DashboardVariableRefreshNever + case "onDashboardLoad": + return dashv2alpha1.DashboardVariableRefreshOnDashboardLoad + case "onTimeRangeChanged": + return dashv2alpha1.DashboardVariableRefreshOnTimeRangeChanged + default: + return dashv2alpha1.DashboardVariableRefreshNever + } + default: + return dashv2alpha1.DashboardVariableRefreshNever + } +} + +func transformVariableSortToEnum(sort interface{}) dashv2alpha1.DashboardVariableSort { + switch v := sort.(type) { + case int: + switch v { + case 0: + return dashv2alpha1.DashboardVariableSortDisabled + case 1: + return dashv2alpha1.DashboardVariableSortAlphabeticalAsc + case 2: + return dashv2alpha1.DashboardVariableSortAlphabeticalDesc + case 3: + return dashv2alpha1.DashboardVariableSortNumericalAsc + case 4: + return dashv2alpha1.DashboardVariableSortNumericalDesc + default: + return dashv2alpha1.DashboardVariableSortDisabled + } + case float64: + return transformVariableSortToEnum(int(v)) + case string: + switch v { + case "disabled", "": + return dashv2alpha1.DashboardVariableSortDisabled + case "alphabeticalAsc": + return dashv2alpha1.DashboardVariableSortAlphabeticalAsc + case "alphabeticalDesc": + return dashv2alpha1.DashboardVariableSortAlphabeticalDesc + case "numericalAsc": + return dashv2alpha1.DashboardVariableSortNumericalAsc + case "numericalDesc": + return dashv2alpha1.DashboardVariableSortNumericalDesc + case "alphabeticalCaseInsensitiveAsc": + return dashv2alpha1.DashboardVariableSortAlphabeticalCaseInsensitiveAsc + case "alphabeticalCaseInsensitiveDesc": + return dashv2alpha1.DashboardVariableSortAlphabeticalCaseInsensitiveDesc + case "naturalAsc": + return dashv2alpha1.DashboardVariableSortNaturalAsc + case "naturalDesc": + return dashv2alpha1.DashboardVariableSortNaturalDesc + default: + return dashv2alpha1.DashboardVariableSortDisabled + } + default: + return dashv2alpha1.DashboardVariableSortDisabled + } +} + +func transformVariables(ctx context.Context, dashboard map[string]interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) ([]dashv2alpha1.DashboardVariableKind, error) { + templating, ok := dashboard["templating"].(map[string]interface{}) + if !ok { + return []dashv2alpha1.DashboardVariableKind{}, nil + } + + list, ok := templating["list"].([]interface{}) + if !ok { + return []dashv2alpha1.DashboardVariableKind{}, nil + } + + variables := make([]dashv2alpha1.DashboardVariableKind, 0, len(list)) + + for _, v := range list { + varMap, ok := v.(map[string]interface{}) + if !ok { + continue + } + + // Extract common properties + commonProps := extractCommonVariableProperties(varMap) + varType := schemaversion.GetStringValue(varMap, "type") + + switch varType { + case "query": + if queryVar, err := buildQueryVariable(ctx, varMap, commonProps, dsInfoProvider); err == nil { + variables = append(variables, queryVar) + } + case "datasource": + if dsVar, err := buildDatasourceVariable(ctx, varMap, commonProps, dsInfoProvider); err == nil { + variables = append(variables, dsVar) + } + case "custom": + if customVar, err := buildCustomVariable(varMap, commonProps); err == nil { + variables = append(variables, customVar) + } + case "adhoc": + if adhocVar, err := buildAdhocVariable(ctx, varMap, commonProps, dsInfoProvider); err == nil { + variables = append(variables, adhocVar) + } + case "constant": + if constantVar, err := buildConstantVariable(varMap, commonProps); err == nil { + variables = append(variables, constantVar) + } + case "interval": + if intervalVar, err := buildIntervalVariable(varMap, commonProps); err == nil { + variables = append(variables, intervalVar) + } + case "textbox": + if textVar, err := buildTextVariable(varMap, commonProps); err == nil { + variables = append(variables, textVar) + } + case "groupby": + if groupByVar, err := buildGroupByVariable(ctx, varMap, commonProps, dsInfoProvider); err == nil { + variables = append(variables, groupByVar) + } + default: + // Skip unknown variable types + continue + } + } + + return variables, nil +} + +// Common variable properties extraction +type CommonVariableProperties struct { + Name string + Label *string + Description *string + Hide dashv2alpha1.DashboardVariableHide + SkipUrlSync bool +} + +func extractCommonVariableProperties(varMap map[string]interface{}) CommonVariableProperties { + props := CommonVariableProperties{ + Name: schemaversion.GetStringValue(varMap, "name"), + Hide: transformVariableHideToEnum(varMap["hide"]), + SkipUrlSync: getBoolField(varMap, "skipUrlSync", false), + } + + if label := schemaversion.GetStringValue(varMap, "label"); label != "" { + props.Label = &label + } + + if description := schemaversion.GetStringValue(varMap, "description"); description != "" { + props.Description = &description + } + + return props +} + +// Helper function to build variable options +func buildVariableOptions(options interface{}) []dashv2alpha1.DashboardVariableOption { + if options == nil { + return []dashv2alpha1.DashboardVariableOption{} + } + + optionsSlice, ok := options.([]interface{}) + if !ok { + return []dashv2alpha1.DashboardVariableOption{} + } + + result := make([]dashv2alpha1.DashboardVariableOption, 0, len(optionsSlice)) + for _, opt := range optionsSlice { + if optMap, ok := opt.(map[string]interface{}); ok { + selected := getBoolField(optMap, "selected", false) + option := dashv2alpha1.DashboardVariableOption{ + Text: buildStringOrArrayOfString(optMap["text"]), + Value: buildStringOrArrayOfString(optMap["value"]), + Selected: &selected, + } + result = append(result, option) + } + } + return result +} + +// Helper function to build DashboardStringOrArrayOfString +func buildStringOrArrayOfString(value interface{}) dashv2alpha1.DashboardStringOrArrayOfString { + switch v := value.(type) { + case string: + return dashv2alpha1.DashboardStringOrArrayOfString{ + String: &v, + } + case []interface{}: + stringArray := make([]string, 0, len(v)) + for _, item := range v { + if str, ok := item.(string); ok { + stringArray = append(stringArray, str) + } + } + return dashv2alpha1.DashboardStringOrArrayOfString{ + ArrayOfString: stringArray, + } + case map[string]interface{}: + // Handle legacy string value format + if legacyValue, ok := v[LEGACY_STRING_VALUE_KEY].(string); ok { + return dashv2alpha1.DashboardStringOrArrayOfString{ + String: &legacyValue, + } + } + // Fallback for other map types + empty := "" + return dashv2alpha1.DashboardStringOrArrayOfString{ + String: &empty, + } + default: + empty := "" + return dashv2alpha1.DashboardStringOrArrayOfString{ + String: &empty, + } + } +} + +// Helper function to build variable current value +func buildVariableCurrent(current interface{}) dashv2alpha1.DashboardVariableOption { + if current == nil { + empty := "" + return dashv2alpha1.DashboardVariableOption{ + Text: dashv2alpha1.DashboardStringOrArrayOfString{String: &empty}, + Value: dashv2alpha1.DashboardStringOrArrayOfString{String: &empty}, + } + } + + if currentMap, ok := current.(map[string]interface{}); ok { + return dashv2alpha1.DashboardVariableOption{ + Text: buildStringOrArrayOfString(currentMap["text"]), + Value: buildStringOrArrayOfString(currentMap["value"]), + } + } + + // Fallback for simple values + empty := "" + return dashv2alpha1.DashboardVariableOption{ + Text: dashv2alpha1.DashboardStringOrArrayOfString{String: &empty}, + Value: dashv2alpha1.DashboardStringOrArrayOfString{String: &empty}, + } +} + +// Helper function to build DataQuery kind +func buildDataQueryKind(query interface{}, datasourceType string) dashv2alpha1.DashboardDataQueryKind { + var querySpec map[string]interface{} + + switch q := query.(type) { + case string: + // Only include LEGACY_STRING_VALUE_KEY if query is a non-empty string + querySpec = make(map[string]interface{}) + if q != "" { + querySpec[LEGACY_STRING_VALUE_KEY] = q + } + case map[string]interface{}: + // Use query directly to match frontend behavior + querySpec = q + default: + querySpec = make(map[string]interface{}) + } + + return dashv2alpha1.DashboardDataQueryKind{ + Kind: "DataQuery", + Spec: querySpec, + } +} + +// Helper function to build DataQuery kind for variables (filters out refId) +func buildDataQueryKindForVariable(query interface{}, datasourceType string) dashv2alpha1.DashboardDataQueryKind { + var querySpec map[string]interface{} + + switch q := query.(type) { + case string: + // Only include LEGACY_STRING_VALUE_KEY if query is a non-empty string + querySpec = make(map[string]interface{}) + if q != "" { + querySpec[LEGACY_STRING_VALUE_KEY] = q + } + case map[string]interface{}: + // Filter out refId for variables (refId should only be in panel queries) + querySpec = q + default: + querySpec = make(map[string]interface{}) + } + + return dashv2alpha1.DashboardDataQueryKind{ + Kind: "DataQuery", + Spec: querySpec, + } +} + +// Query Variable +func buildQueryVariable(ctx context.Context, varMap map[string]interface{}, commonProps CommonVariableProperties, dsInfoProvider schemaversion.DataSourceInfoProvider) (dashv2alpha1.DashboardVariableKind, error) { + datasource := varMap["datasource"] + var datasourceType, datasourceUID string + + if ds, ok := datasource.(map[string]interface{}); ok { + datasourceUID = schemaversion.GetStringValue(ds, "uid") + datasourceType = schemaversion.GetStringValue(ds, "type") + + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if datasourceUID != "" && datasourceType == "" && !isTemplateVariable(datasourceUID) { + datasourceType = getDatasourceTypeByUID(ctx, datasourceUID, dsInfoProvider) + } else if datasourceUID == "" && datasourceType == "" { + // If no UID and no type, use default + datasourceType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + } else { + datasourceType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + + queryVar := &dashv2alpha1.DashboardQueryVariableKind{ + Kind: "QueryVariable", + Spec: dashv2alpha1.DashboardQueryVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + Current: buildVariableCurrent(varMap["current"]), + Multi: getBoolField(varMap, "multi", false), + IncludeAll: getBoolField(varMap, "includeAll", false), + Refresh: transformVariableRefreshToEnum(varMap["refresh"]), + Sort: transformVariableSortToEnum(varMap["sort"]), + Regex: schemaversion.GetStringValue(varMap, "regex"), + Query: buildDataQueryKindForVariable(varMap["query"], datasourceType), + AllowCustomValue: getBoolField(varMap, "allowCustomValue", true), + }, + } + + // Only set definition if it exists in the input (match frontend behavior) + if def, exists := varMap["definition"]; exists && def != nil { + if defStr, ok := def.(string); ok { + queryVar.Spec.Definition = &defStr + } + } + + // Only include datasource if datasourceUID exists (matching frontend behavior) + if datasourceUID != "" { + dsRef := &dashv2alpha1.DashboardDataSourceRef{ + Type: &datasourceType, + Uid: &datasourceUID, + } + queryVar.Spec.Datasource = dsRef + } + + // Always set options (matching frontend behavior) + queryVar.Spec.Options = buildVariableOptions(varMap["options"]) + + if allValue := schemaversion.GetStringValue(varMap, "allValue"); allValue != "" { + queryVar.Spec.AllValue = &allValue + } + + return dashv2alpha1.DashboardVariableKind{ + QueryVariableKind: queryVar, + }, nil +} + +// Datasource Variable +func buildDatasourceVariable(ctx context.Context, varMap map[string]interface{}, commonProps CommonVariableProperties, dsInfoProvider schemaversion.DataSourceInfoProvider) (dashv2alpha1.DashboardVariableKind, error) { + pluginId := getDefaultDatasourceType(ctx, dsInfoProvider) + if query := varMap["query"]; query != nil { + if queryStr, ok := query.(string); ok { + pluginId = queryStr + } + } + + dsVar := &dashv2alpha1.DashboardDatasourceVariableKind{ + Kind: "DatasourceVariable", + Spec: dashv2alpha1.DashboardDatasourceVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + PluginId: pluginId, + Current: buildVariableCurrent(varMap["current"]), + Multi: getBoolField(varMap, "multi", false), + IncludeAll: getBoolField(varMap, "includeAll", false), + Refresh: transformVariableRefreshToEnum(varMap["refresh"]), + Regex: schemaversion.GetStringValue(varMap, "regex"), + AllowCustomValue: getBoolField(varMap, "allowCustomValue", true), + }, + } + + // Always set options (matching frontend behavior) + dsVar.Spec.Options = buildVariableOptions(varMap["options"]) + + if allValue := schemaversion.GetStringValue(varMap, "allValue"); allValue != "" { + dsVar.Spec.AllValue = &allValue + } + + return dashv2alpha1.DashboardVariableKind{ + DatasourceVariableKind: dsVar, + }, nil +} + +// Custom Variable +func buildCustomVariable(varMap map[string]interface{}, commonProps CommonVariableProperties) (dashv2alpha1.DashboardVariableKind, error) { + customVar := &dashv2alpha1.DashboardCustomVariableKind{ + Kind: "CustomVariable", + Spec: dashv2alpha1.DashboardCustomVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + Query: schemaversion.GetStringValue(varMap, "query"), + Current: buildVariableCurrent(varMap["current"]), + Multi: getBoolField(varMap, "multi", false), + IncludeAll: getBoolField(varMap, "includeAll", false), + AllowCustomValue: getBoolField(varMap, "allowCustomValue", true), + }, + } + + // Always set options (matching frontend behavior) + customVar.Spec.Options = buildVariableOptions(varMap["options"]) + + if allValue := schemaversion.GetStringValue(varMap, "allValue"); allValue != "" { + customVar.Spec.AllValue = &allValue + } + + return dashv2alpha1.DashboardVariableKind{ + CustomVariableKind: customVar, + }, nil +} + +// Constant Variable +func buildConstantVariable(varMap map[string]interface{}, commonProps CommonVariableProperties) (dashv2alpha1.DashboardVariableKind, error) { + // Get query value - if missing, it should be undefined (not empty string) + // Frontend uses variable.query for value, so if query is missing, value is undefined + var queryValue interface{} + if query, exists := varMap["query"]; exists { + if queryStr, ok := query.(string); ok { + queryValue = queryStr + } else { + queryValue = query + } + } + + // Build current from query value (matching frontend: value: variable.query, text: String(variable.state.value)) + // If query is undefined, frontend produces: text: "undefined", value: undefined (removed), query: undefined (removed) + var current dashv2alpha1.DashboardVariableOption + if currentVal, exists := varMap["current"]; exists && currentVal != nil { + // Use provided current if it exists + current = buildVariableCurrent(currentVal) + } else if queryValue != nil { + // Build current from query value (matching frontend behavior) + queryStr := "" + if str, ok := queryValue.(string); ok { + queryStr = str + } + current = dashv2alpha1.DashboardVariableOption{ + Text: dashv2alpha1.DashboardStringOrArrayOfString{String: &queryStr}, + Value: dashv2alpha1.DashboardStringOrArrayOfString{String: &queryStr}, + } + } else { + // query is undefined - match frontend: value ? String(value) : '' (defaults to empty string, not "undefined") + emptyStr := "" + current = dashv2alpha1.DashboardVariableOption{ + Text: dashv2alpha1.DashboardStringOrArrayOfString{String: &emptyStr}, + Value: dashv2alpha1.DashboardStringOrArrayOfString{String: &emptyStr}, + } + } + + // Get query value - frontend sets query: variable.state.value which can be undefined + // Since Query is a required string field, we must set it, but when query is missing from v1, + // we set it to empty string (frontend would have undefined which gets removed by sortedDeepCloneWithoutNulls) + var query string + if queryValue != nil { + if queryStr, ok := queryValue.(string); ok { + query = queryStr + } + } + // If queryValue is nil, query remains empty string "" + + constantVar := &dashv2alpha1.DashboardConstantVariableKind{ + Kind: "ConstantVariable", + Spec: dashv2alpha1.DashboardConstantVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + Query: query, + Current: current, + }, + } + + return dashv2alpha1.DashboardVariableKind{ + ConstantVariableKind: constantVar, + }, nil +} + +// Interval Variable +func buildIntervalVariable(varMap map[string]interface{}, commonProps CommonVariableProperties) (dashv2alpha1.DashboardVariableKind, error) { + query := schemaversion.GetStringValue(varMap, "query") + + // If query is missing/empty but options exist, generate query from options (matches frontend behavior) + if query == "" { + if options, ok := varMap["options"].([]interface{}); ok && len(options) > 0 { + queryValues := make([]string, 0, len(options)) + for _, opt := range options { + if optMap, ok := opt.(map[string]interface{}); ok { + if value, exists := optMap["value"]; exists { + if valueStr, ok := value.(string); ok && valueStr != "" { + queryValues = append(queryValues, valueStr) + } + } else if text, exists := optMap["text"]; exists { + if textStr, ok := text.(string); ok && textStr != "" { + queryValues = append(queryValues, textStr) + } + } + } + } + if len(queryValues) > 0 { + // Join interval values with comma, matching frontend getIntervalsQueryFromNewIntervalModel + query = strings.Join(queryValues, ",") + } + } + } + + intervalVar := &dashv2alpha1.DashboardIntervalVariableKind{ + Kind: "IntervalVariable", + Spec: dashv2alpha1.DashboardIntervalVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + Query: query, + Current: buildVariableCurrent(varMap["current"]), + Refresh: dashv2alpha1.DashboardVariableRefreshOnTimeRangeChanged, // Always onTimeRangeChanged for interval + Auto: getBoolField(varMap, "auto", false), + AutoMin: schemaversion.GetStringValue(varMap, "auto_min"), + AutoCount: int64(getIntField(varMap, "auto_count", 0)), + }, + } + + // Always set options (matching frontend behavior) + intervalVar.Spec.Options = buildVariableOptions(varMap["options"]) + + return dashv2alpha1.DashboardVariableKind{ + IntervalVariableKind: intervalVar, + }, nil +} + +// Text Variable +func buildTextVariable(varMap map[string]interface{}, commonProps CommonVariableProperties) (dashv2alpha1.DashboardVariableKind, error) { + textVar := &dashv2alpha1.DashboardTextVariableKind{ + Kind: "TextVariable", + Spec: dashv2alpha1.DashboardTextVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + Query: schemaversion.GetStringValue(varMap, "query"), + Current: buildVariableCurrent(varMap["current"]), + }, + } + + return dashv2alpha1.DashboardVariableKind{ + TextVariableKind: textVar, + }, nil +} + +// Adhoc Variable +func buildAdhocVariable(ctx context.Context, varMap map[string]interface{}, commonProps CommonVariableProperties, dsInfoProvider schemaversion.DataSourceInfoProvider) (dashv2alpha1.DashboardVariableKind, error) { + datasource := varMap["datasource"] + var datasourceType, datasourceUID string + + if ds, ok := datasource.(map[string]interface{}); ok { + datasourceUID = schemaversion.GetStringValue(ds, "uid") + datasourceType = schemaversion.GetStringValue(ds, "type") + + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if datasourceUID != "" && datasourceType == "" && !isTemplateVariable(datasourceUID) { + datasourceType = getDatasourceTypeByUID(ctx, datasourceUID, dsInfoProvider) + } else if datasourceUID == "" && datasourceType == "" { + // If no UID and no type, use default + datasourceType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + } else { + datasourceType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + + adhocVar := &dashv2alpha1.DashboardAdhocVariableKind{ + Kind: "AdhocVariable", + Spec: dashv2alpha1.DashboardAdhocVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + AllowCustomValue: getBoolField(varMap, "allowCustomValue", true), + }, + } + + // Transform baseFilters if they exist + if baseFilters, exists := varMap["baseFilters"]; exists { + if baseFiltersArray, ok := baseFilters.([]interface{}); ok { + adhocVar.Spec.BaseFilters = transformAdHocFilters(baseFiltersArray) + } + } + + // Transform filters if they exist + if filters, exists := varMap["filters"]; exists { + if filtersArray, ok := filters.([]interface{}); ok { + adhocVar.Spec.Filters = transformAdHocFilters(filtersArray) + } + } + + // Transform defaultKeys if they exist + if defaultKeys, exists := varMap["defaultKeys"]; exists { + if defaultKeysArray, ok := defaultKeys.([]interface{}); ok { + adhocVar.Spec.DefaultKeys = transformMetricFindValues(defaultKeysArray) + } + } + + // Only include datasource if datasourceUID exists (matching frontend behavior) + if datasourceUID != "" { + dsRef := &dashv2alpha1.DashboardDataSourceRef{ + Type: &datasourceType, + Uid: &datasourceUID, + } + adhocVar.Spec.Datasource = dsRef + } + + return dashv2alpha1.DashboardVariableKind{ + AdhocVariableKind: adhocVar, + }, nil +} + +// Helper function to transform adhoc filters +func transformAdHocFilters(filters []interface{}) []dashv2alpha1.DashboardAdHocFilterWithLabels { + result := make([]dashv2alpha1.DashboardAdHocFilterWithLabels, 0, len(filters)) + + for _, filter := range filters { + if filterMap, ok := filter.(map[string]interface{}); ok { + // Only include filters that don't have an origin or have origin "dashboard" + // This matches the frontend validateFiltersOrigin logic + if origin, exists := filterMap["origin"]; !exists || origin == "dashboard" { + adhocFilter := dashv2alpha1.DashboardAdHocFilterWithLabels{ + Key: schemaversion.GetStringValue(filterMap, "key"), + Operator: schemaversion.GetStringValue(filterMap, "operator", "="), + Value: schemaversion.GetStringValue(filterMap, "value"), + } + + // Handle optional fields + if keyLabel := schemaversion.GetStringValue(filterMap, "keyLabel"); keyLabel != "" { + adhocFilter.KeyLabel = &keyLabel + } + if condition := schemaversion.GetStringValue(filterMap, "condition"); condition != "" { + adhocFilter.Condition = &condition + } + + // Handle multi-value filters + if values, exists := filterMap["values"]; exists { + if valuesArray, ok := values.([]interface{}); ok { + stringValues := make([]string, 0, len(valuesArray)) + for _, v := range valuesArray { + if str, ok := v.(string); ok { + stringValues = append(stringValues, str) + } + } + if len(stringValues) > 0 { + adhocFilter.Values = stringValues + } + } + } + + // Handle value labels for multi-value filters + if valueLabels, exists := filterMap["valueLabels"]; exists { + if valueLabelsArray, ok := valueLabels.([]interface{}); ok { + stringValueLabels := make([]string, 0, len(valueLabelsArray)) + for _, v := range valueLabelsArray { + if str, ok := v.(string); ok { + stringValueLabels = append(stringValueLabels, str) + } + } + if len(stringValueLabels) > 0 { + adhocFilter.ValueLabels = stringValueLabels + } + } + } + + result = append(result, adhocFilter) + } + } + } + + return result +} + +// Helper function to transform metric find values +func transformMetricFindValues(values []interface{}) []dashv2alpha1.DashboardMetricFindValue { + result := make([]dashv2alpha1.DashboardMetricFindValue, 0, len(values)) + + for _, value := range values { + if valueMap, ok := value.(map[string]interface{}); ok { + // Convert object format to string format to match frontend behavior + text := schemaversion.GetStringValue(valueMap, "text") + if text != "" { + // Use text as the string value to match frontend behavior + metricFindValue := dashv2alpha1.DashboardMetricFindValue{ + Text: text, + Value: &dashv2alpha1.DashboardStringOrFloat64{ + String: &text, + }, + } + result = append(result, metricFindValue) + } + } else if str, ok := value.(string); ok { + // Handle simple string values - convert to object format with text and value + metricFindValue := dashv2alpha1.DashboardMetricFindValue{ + Text: str, + Value: &dashv2alpha1.DashboardStringOrFloat64{ + String: &str, + }, + } + result = append(result, metricFindValue) + } + } + + return result +} + +// GroupBy Variable +func buildGroupByVariable(ctx context.Context, varMap map[string]interface{}, commonProps CommonVariableProperties, dsInfoProvider schemaversion.DataSourceInfoProvider) (dashv2alpha1.DashboardVariableKind, error) { + datasource := varMap["datasource"] + var datasourceType, datasourceUID string + + if ds, ok := datasource.(map[string]interface{}); ok { + datasourceUID = schemaversion.GetStringValue(ds, "uid") + datasourceType = schemaversion.GetStringValue(ds, "type") + + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if datasourceUID != "" && datasourceType == "" && !isTemplateVariable(datasourceUID) { + datasourceType = getDatasourceTypeByUID(ctx, datasourceUID, dsInfoProvider) + } else if datasourceUID == "" && datasourceType == "" { + // If no UID and no type, use default + datasourceType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + } else { + datasourceType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + + groupByVar := &dashv2alpha1.DashboardGroupByVariableKind{ + Kind: "GroupByVariable", + Spec: dashv2alpha1.DashboardGroupByVariableSpec{ + Name: commonProps.Name, + Label: commonProps.Label, + Description: commonProps.Description, + Hide: commonProps.Hide, + SkipUrlSync: commonProps.SkipUrlSync, + Current: buildVariableCurrent(varMap["current"]), + Multi: getBoolField(varMap, "multi", false), + }, + } + + // Only include datasource if datasourceUID exists + if datasourceUID != "" { + dsRef := &dashv2alpha1.DashboardDataSourceRef{ + Type: &datasourceType, + Uid: &datasourceUID, + } + groupByVar.Spec.Datasource = dsRef + } + + // Always set options (matching frontend behavior) + groupByVar.Spec.Options = buildVariableOptions(varMap["options"]) + + return dashv2alpha1.DashboardVariableKind{ + GroupByVariableKind: groupByVar, + }, nil +} + +func transformAnnotations(dashboard map[string]interface{}) ([]dashv2alpha1.DashboardAnnotationQueryKind, error) { + annotations, ok := dashboard["annotations"].(map[string]interface{}) + if !ok { + // Return empty array to match frontend behavior + return []dashv2alpha1.DashboardAnnotationQueryKind{}, nil + } + + list, ok := annotations["list"].([]interface{}) + if !ok || len(list) == 0 { + // Return empty array to match frontend behavior + return []dashv2alpha1.DashboardAnnotationQueryKind{}, nil + } + + result := make([]dashv2alpha1.DashboardAnnotationQueryKind, 0, len(list)) + + for _, a := range list { + annotationMap, ok := a.(map[string]interface{}) + if !ok { + continue + } + + if annotationQuery, err := buildAnnotationQuery(annotationMap); err == nil { + result = append(result, annotationQuery) + } + } + + return result, nil +} + +func buildAnnotationQuery(annotationMap map[string]interface{}) (dashv2alpha1.DashboardAnnotationQueryKind, error) { + // Extract datasource information + var datasourceRef *dashv2alpha1.DashboardDataSourceRef + var datasourceType, datasourceUID string + + if datasource, ok := annotationMap["datasource"].(map[string]interface{}); ok { + datasourceUID = schemaversion.GetStringValue(datasource, "uid") + datasourceType = schemaversion.GetStringValue(datasource, "type") + + // Only create datasource ref if the annotation actually has a valid datasource type in the original input + // This matches frontend behavior: if (dsUID && annotation.datasource && annotation.datasource.type) + // We don't try to resolve datasources that are missing a type - they should be resolved at runtime + if datasourceUID != "" && datasourceType != "" { + datasourceRef = &dashv2alpha1.DashboardDataSourceRef{ + Type: &datasourceType, + Uid: &datasourceUID, + } + } + } + + // Build the query from target + var query *dashv2alpha1.DashboardDataQueryKind + if target, ok := annotationMap["target"].(map[string]interface{}); ok && target != nil { + queryKind := dashv2alpha1.DashboardDataQueryKind{ + Kind: "DataQuery", + Spec: target, + } + // Group field is not available in v2alpha1 DashboardDataQueryKind + query = &queryKind + } else { + // Always provide a query to match frontend behavior + queryKind := dashv2alpha1.DashboardDataQueryKind{ + Kind: "DataQuery", + Spec: map[string]interface{}{}, + } + // Group field is not available in v2alpha1 DashboardDataQueryKind + query = &queryKind + } + + // Transform filter + var filter *dashv2alpha1.DashboardAnnotationPanelFilter + if filterMap, ok := annotationMap["filter"].(map[string]interface{}); ok && filterMap != nil { + filter = buildAnnotationFilter(filterMap) + } + + // Transform builtIn from float64 to bool + var builtInPtr *bool + if builtInVal, ok := annotationMap["builtIn"]; ok && builtInVal != nil { + switch v := builtInVal.(type) { + case float64: + val := v != 0 + builtInPtr = &val + case int: + val := v != 0 + builtInPtr = &val + case bool: + builtInPtr = &v + } + } + + defaultAnnotationQuerySpec := dashv2alpha1.NewDashboardAnnotationQuerySpec() + spec := dashv2alpha1.DashboardAnnotationQuerySpec{ + Name: schemaversion.GetStringValue(annotationMap, "name"), + Datasource: datasourceRef, + Query: query, + Enable: getBoolField(annotationMap, "enable", defaultAnnotationQuerySpec.Enable), // Default to false to match frontend: Boolean(annotation.enable) || Boolean(override?.enable) + Hide: getBoolField(annotationMap, "hide", defaultAnnotationQuerySpec.Hide), + IconColor: schemaversion.GetStringValue(annotationMap, "iconColor", defaultAnnotationQuerySpec.IconColor), + BuiltIn: builtInPtr, + Filter: filter, + } + + // Handle any additional properties in LegacyOptions + legacyOptions := make(map[string]interface{}) + if annotationType := schemaversion.GetStringValue(annotationMap, "type"); annotationType != "" { + legacyOptions["type"] = annotationType + } + + // Add other legacy fields if they exist + for key, value := range annotationMap { + switch key { + case "name", "datasource", "enable", "hide", "iconColor", "filter", "target", "builtIn", "type": + // Skip already handled fields + default: + legacyOptions[key] = value + } + } + + if len(legacyOptions) > 0 { + spec.LegacyOptions = legacyOptions + } + + return dashv2alpha1.DashboardAnnotationQueryKind{ + Kind: "AnnotationQuery", + Spec: spec, + }, nil +} + +func buildAnnotationFilter(filterMap map[string]interface{}) *dashv2alpha1.DashboardAnnotationPanelFilter { + filter := &dashv2alpha1.DashboardAnnotationPanelFilter{ + Ids: []uint32{}, + } + + if exclude := getBoolField(filterMap, "exclude", false); exclude { + filter.Exclude = &exclude + } + + if ids, ok := filterMap["ids"].([]interface{}); ok { + uintIds := make([]uint32, 0, len(ids)) + for _, id := range ids { + switch v := id.(type) { + case float64: + if v >= 0 && v <= float64(^uint32(0)) { + uintIds = append(uintIds, uint32(v)) + } + case int: + if v >= 0 && uint64(v) <= math.MaxUint32 { + uintIds = append(uintIds, uint32(v)) + } + } + } + filter.Ids = uintIds + } + + return filter +} + +// Panel helper functions + +func transformPanelQueries(ctx context.Context, panelMap map[string]interface{}, dsInfoProvider schemaversion.DataSourceInfoProvider) []dashv2alpha1.DashboardPanelQueryKind { + targets, ok := panelMap["targets"].([]interface{}) + if !ok { + return []dashv2alpha1.DashboardPanelQueryKind{} + } + + // Get panel datasource + var panelDatasource *dashv2alpha1.DashboardDataSourceRef + if ds, ok := panelMap["datasource"].(map[string]interface{}); ok { + dsUID := schemaversion.GetStringValue(ds, "uid") + dsType := schemaversion.GetStringValue(ds, "type") + + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if dsUID != "" && dsType == "" && !isTemplateVariable(dsUID) { + dsType = getDatasourceTypeByUID(ctx, dsUID, dsInfoProvider) + } else if dsUID == "" && dsType == "" { + // If no UID and no type, use default + dsType = getDefaultDatasourceType(ctx, dsInfoProvider) + } + + panelDatasource = &dashv2alpha1.DashboardDataSourceRef{ + Type: &dsType, + Uid: &dsUID, + } + } + + queries := make([]dashv2alpha1.DashboardPanelQueryKind, 0, len(targets)) + + for _, target := range targets { + if targetMap, ok := target.(map[string]interface{}); ok { + query := transformSingleQuery(ctx, targetMap, panelDatasource, dsInfoProvider) + queries = append(queries, query) + } + } + + return queries +} + +func transformSingleQuery(ctx context.Context, targetMap map[string]interface{}, panelDatasource *dashv2alpha1.DashboardDataSourceRef, dsInfoProvider schemaversion.DataSourceInfoProvider) dashv2alpha1.DashboardPanelQueryKind { + refId := schemaversion.GetStringValue(targetMap, "refId", "A") + hidden := getBoolField(targetMap, "hide", false) + + // Extract datasource from query or use panel datasource + var queryDatasourceType string + var queryDatasourceUID string + if ds, ok := targetMap["datasource"].(map[string]interface{}); ok { + queryDatasourceUID = schemaversion.GetStringValue(ds, "uid") + queryDatasourceType = schemaversion.GetStringValue(ds, "type") + + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if queryDatasourceUID != "" && queryDatasourceType == "" && !isTemplateVariable(queryDatasourceUID) { + queryDatasourceType = getDatasourceTypeByUID(ctx, queryDatasourceUID, dsInfoProvider) + } + } else if panelDatasource != nil { + // Only use panel datasource if it's not a mixed datasource + // Mixed datasources should not be propagated to individual queries + if panelDatasource.Uid != nil && *panelDatasource.Uid != "-- Mixed --" { + if panelDatasource.Type != nil { + queryDatasourceType = *panelDatasource.Type + } + queryDatasourceUID = *panelDatasource.Uid + } + } + + // Build query spec by excluding known fields + querySpec := make(map[string]interface{}) + for key, value := range targetMap { + switch key { + case "refId", "hide", "datasource": + // Skip these fields as they're handled separately + default: + querySpec[key] = value + } + } + + // Create the panel query spec + panelQuerySpec := dashv2alpha1.DashboardPanelQuerySpec{ + RefId: refId, + Hidden: hidden, + Query: buildDataQueryKind(querySpec, queryDatasourceType), + } + + // Only include datasource reference if UID is provided + if queryDatasourceUID != "" { + panelQuerySpec.Datasource = &dashv2alpha1.DashboardDataSourceRef{ + Type: &queryDatasourceType, + Uid: &queryDatasourceUID, + } + } + + return dashv2alpha1.DashboardPanelQueryKind{ + Kind: "PanelQuery", + Spec: panelQuerySpec, + } +} + +func transformPanelTransformations(panelMap map[string]interface{}) []dashv2alpha1.DashboardTransformationKind { + transformations, ok := panelMap["transformations"].([]interface{}) + if !ok { + return []dashv2alpha1.DashboardTransformationKind{} + } + + result := make([]dashv2alpha1.DashboardTransformationKind, 0, len(transformations)) + for _, t := range transformations { + if tMap, ok := t.(map[string]interface{}); ok { + // Extract the transformation ID + transformationId := schemaversion.GetStringValue(tMap, "id") + + // Extract the options field specifically + var options interface{} + if opts, exists := tMap["options"]; exists { + options = opts + } + + transformationKind := dashv2alpha1.DashboardTransformationKind{ + Kind: transformationId, + Spec: dashv2alpha1.DashboardDataTransformerConfig{ + Id: transformationId, + Options: options, + }, + } + result = append(result, transformationKind) + } + } + + return result +} + +func buildQueryOptions(panelMap map[string]interface{}) dashv2alpha1.DashboardQueryOptionsSpec { + queryOptions := dashv2alpha1.DashboardQueryOptionsSpec{} + + if cacheTimeout := schemaversion.GetStringValue(panelMap, "cacheTimeout"); cacheTimeout != "" { + queryOptions.CacheTimeout = &cacheTimeout + } + if maxDataPoints := getIntField(panelMap, "maxDataPoints", 0); maxDataPoints > 0 { + maxDP := int64(maxDataPoints) + queryOptions.MaxDataPoints = &maxDP + } + if interval := schemaversion.GetStringValue(panelMap, "interval"); interval != "" { + queryOptions.Interval = &interval + } + if hideTimeOverride := getBoolField(panelMap, "hideTimeOverride", false); hideTimeOverride { + queryOptions.HideTimeOverride = &hideTimeOverride + } + // Handle queryCachingTTL as string or number + if val, exists := panelMap["queryCachingTTL"]; exists { + switch v := val.(type) { + case string: + if v != "" { + if ttl, err := strconv.ParseInt(v, 10, 64); err == nil { + queryOptions.QueryCachingTTL = &ttl + } + } + case float64: + ttl := int64(v) + queryOptions.QueryCachingTTL = &ttl + case int: + ttl := int64(v) + queryOptions.QueryCachingTTL = &ttl + } + } + if timeFrom := schemaversion.GetStringValue(panelMap, "timeFrom"); timeFrom != "" { + queryOptions.TimeFrom = &timeFrom + } + if timeShift := schemaversion.GetStringValue(panelMap, "timeShift"); timeShift != "" { + queryOptions.TimeShift = &timeShift + } + + return queryOptions +} + +func transformDataLinks(panelMap map[string]interface{}) []dashv2alpha1.DashboardDataLink { + links, ok := panelMap["links"].([]interface{}) + if !ok { + return []dashv2alpha1.DashboardDataLink{} + } + + result := make([]dashv2alpha1.DashboardDataLink, 0, len(links)) + for _, link := range links { + if linkMap, ok := link.(map[string]interface{}); ok { + dataLink := dashv2alpha1.DashboardDataLink{ + Title: schemaversion.GetStringValue(linkMap, "title"), + Url: schemaversion.GetStringValue(linkMap, "url"), + } + if _, exists := linkMap["targetBlank"]; exists { + targetBlank := getBoolField(linkMap, "targetBlank", false) + dataLink.TargetBlank = &targetBlank + } + + result = append(result, dataLink) + } + } + + return result +} + +func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizConfigKind { + panelType := schemaversion.GetStringValue(panelMap, "type", "timeseries") + pluginVersion := schemaversion.GetStringValue(panelMap, "pluginVersion") + + // Extract field config and options + fieldConfig := make(map[string]interface{}) + if fc, ok := panelMap["fieldConfig"].(map[string]interface{}); ok { + fieldConfig = fc + } + + options := make(map[string]interface{}) + if opts, ok := panelMap["options"].(map[string]interface{}); ok { + options = opts + } + + // Add frontend-style default options to match frontend behavior + if legend, ok := options["legend"].(map[string]interface{}); ok { + // Add showLegend: true to match frontend behavior + showLegend := getBoolField(legend, "showLegend", true) + legend["showLegend"] = showLegend + options["legend"] = legend + } + + // Build field config by mapping each field individually + fieldConfigSource := extractFieldConfigSource(fieldConfig) + + return dashv2alpha1.DashboardVizConfigKind{ + Kind: panelType, // Use panelType as Kind (plugin ID) to match schema comment + Spec: dashv2alpha1.DashboardVizConfigSpec{ + PluginVersion: pluginVersion, + FieldConfig: fieldConfigSource, + Options: options, + }, + } +} + +func extractFieldConfigSource(fieldConfig map[string]interface{}) dashv2alpha1.DashboardFieldConfigSource { + // Always initialize with empty defaults to match frontend behavior + fieldConfigSource := dashv2alpha1.DashboardFieldConfigSource{ + Defaults: dashv2alpha1.DashboardFieldConfig{}, + Overrides: []dashv2alpha1.DashboardV2alpha1FieldConfigSourceOverrides{}, + } + + if defaults, ok := fieldConfig["defaults"].(map[string]interface{}); ok { + fieldConfigDefaults := extractFieldConfigDefaults(defaults) + fieldConfigSource.Defaults = fieldConfigDefaults + } + + // Handle overrides + fieldConfigSource.Overrides = extractFieldConfigOverrides(fieldConfig) + + return fieldConfigSource +} + +// Helper functions for extracting field config values +func extractFloat64Field(defaults map[string]interface{}, key string) (*float64, bool) { + if val, exists := defaults[key]; exists { + if floatVal, ok := val.(float64); ok { + return &floatVal, true + } + } + return nil, false +} + +func extractStringField(defaults map[string]interface{}, key string) (*string, bool) { + if val, exists := defaults[key]; exists { + if strVal, ok := val.(string); ok { + return &strVal, true + } + } + return nil, false +} + +func extractBoolField(defaults map[string]interface{}, key string) (*bool, bool) { + if val, exists := defaults[key]; exists { + if boolVal, ok := val.(bool); ok { + return &boolVal, true + } + } + return nil, false +} + +func extractArrayField(defaults map[string]interface{}, key string) ([]interface{}, bool) { + if val, exists := defaults[key]; exists { + if arrayVal, ok := val.([]interface{}); ok { + return arrayVal, true + } + } + return nil, false +} + +func extractMapField(defaults map[string]interface{}, key string) (map[string]interface{}, bool) { + if val, exists := defaults[key]; exists { + if mapVal, ok := val.(map[string]interface{}); ok { + return mapVal, true + } + } + return nil, false +} + +func extractFieldConfigDefaults(defaults map[string]interface{}) dashv2alpha1.DashboardFieldConfig { + fieldConfigDefaults := dashv2alpha1.DashboardFieldConfig{} + hasDefaults := false + + // Extract color + if colorMap, ok := extractMapField(defaults, "color"); ok { + fieldConfigDefaults.Color = buildFieldColor(colorMap) + hasDefaults = true + } + + // Extract custom + if customMap, ok := extractMapField(defaults, "custom"); ok { + fieldConfigDefaults.Custom = customMap + hasDefaults = true + } + + // Extract numeric fields + if val, ok := extractFloat64Field(defaults, "decimals"); ok { + fieldConfigDefaults.Decimals = val + hasDefaults = true + } + if val, ok := extractFloat64Field(defaults, "max"); ok { + fieldConfigDefaults.Max = val + hasDefaults = true + } + if val, ok := extractFloat64Field(defaults, "min"); ok { + fieldConfigDefaults.Min = val + hasDefaults = true + } + + // Extract string fields + if val, ok := extractStringField(defaults, "description"); ok { + fieldConfigDefaults.Description = val + hasDefaults = true + } + if val, ok := extractStringField(defaults, "displayName"); ok { + fieldConfigDefaults.DisplayName = val + hasDefaults = true + } + if val, ok := extractStringField(defaults, "displayNameFromDS"); ok { + fieldConfigDefaults.DisplayNameFromDS = val + hasDefaults = true + } + if val, ok := extractStringField(defaults, "noValue"); ok { + fieldConfigDefaults.NoValue = val + hasDefaults = true + } + if val, ok := extractStringField(defaults, "path"); ok { + fieldConfigDefaults.Path = val + hasDefaults = true + } + if val, ok := extractStringField(defaults, "unit"); ok { + fieldConfigDefaults.Unit = val + hasDefaults = true + } + + // Extract bool fields + if val, ok := extractBoolField(defaults, "filterable"); ok { + fieldConfigDefaults.Filterable = val + hasDefaults = true + } + if val, ok := extractBoolField(defaults, "writeable"); ok { + fieldConfigDefaults.Writeable = val + hasDefaults = true + } + + // Extract array field + if linksArray, ok := extractArrayField(defaults, "links"); ok { + fieldConfigDefaults.Links = linksArray + hasDefaults = true + } + + // Extract mappings + if mappings, exists := defaults["mappings"]; exists { + resultMappings := buildValueMappings(mappings) + fieldConfigDefaults.Mappings = resultMappings + hasDefaults = true + } + + // Extract thresholds + if thresholdsMap, ok := extractMapField(defaults, "thresholds"); ok { + thresholdsConfig := buildThresholdsConfig(thresholdsMap) + fieldConfigDefaults.Thresholds = thresholdsConfig + hasDefaults = true + } + + // Add frontend-style default custom field to match frontend behavior + if !hasDefaults { + fieldConfigDefaults.Custom = map[string]interface{}{} + } + + return fieldConfigDefaults +} + +func buildFieldColor(colorMap map[string]interface{}) *dashv2alpha1.DashboardFieldColor { + var fieldColor dashv2alpha1.DashboardFieldColor + + // mode (required) + if mode, ok := colorMap["mode"].(string); ok { + fieldColor.Mode = dashv2alpha1.DashboardFieldColorModeId(mode) + } + + // fixedColor (optional) + if fixedColor, ok := colorMap["fixedColor"].(string); ok { + fieldColor.FixedColor = &fixedColor + } + + // seriesBy (optional) + if seriesBy, ok := colorMap["seriesBy"].(string); ok { + sb := dashv2alpha1.DashboardFieldColorSeriesByMode(seriesBy) + fieldColor.SeriesBy = &sb + } + + return &fieldColor +} + +func buildValueMappings(mappings interface{}) []dashv2alpha1.DashboardValueMapping { + var resultMappings []dashv2alpha1.DashboardValueMapping + + mappingsArr, ok := mappings.([]interface{}) + if !ok || len(mappingsArr) == 0 { + return resultMappings + } + + for _, mapping := range mappingsArr { + mappingMap, ok := mapping.(map[string]interface{}) + if !ok { + continue + } + + typ, ok := mappingMap["type"].(string) + if !ok { + continue + } + + switch typ { + case "value": + if valueMapping := buildValueMap(mappingMap); valueMapping != nil { + resultMappings = append(resultMappings, dashv2alpha1.DashboardValueMapping{ + ValueMap: valueMapping, + }) + } + case "range": + if rangeMapping := buildRangeMap(mappingMap); rangeMapping != nil { + resultMappings = append(resultMappings, dashv2alpha1.DashboardValueMapping{ + RangeMap: rangeMapping, + }) + } + case "regex": + if regexMapping := buildRegexMap(mappingMap); regexMapping != nil { + resultMappings = append(resultMappings, dashv2alpha1.DashboardValueMapping{ + RegexMap: regexMapping, + }) + } + case "special": + if specialMapping := buildSpecialValueMap(mappingMap); specialMapping != nil { + resultMappings = append(resultMappings, dashv2alpha1.DashboardValueMapping{ + SpecialValueMap: specialMapping, + }) + } + } + } + + return resultMappings +} + +func buildValueMap(mappingMap map[string]interface{}) *dashv2alpha1.DashboardValueMap { + valMap := &dashv2alpha1.DashboardValueMap{} + valMap.Type = dashv2alpha1.DashboardMappingTypeValue + + opts, ok := mappingMap["options"].(map[string]interface{}) + if !ok { + return valMap + } + + valMap.Options = make(map[string]dashv2alpha1.DashboardValueMappingResult) + for k, v := range opts { + resMap, ok := v.(map[string]interface{}) + if !ok { + continue + } + res := buildValueMappingResult(resMap) + valMap.Options[k] = res + } + + return valMap +} + +func buildRangeMap(mappingMap map[string]interface{}) *dashv2alpha1.DashboardRangeMap { + rangeMap := &dashv2alpha1.DashboardRangeMap{} + rangeMap.Type = dashv2alpha1.DashboardMappingTypeRange + + opts, ok := mappingMap["options"].(map[string]interface{}) + if !ok || len(opts) == 0 { + return nil + } + + // Return nil only if both "from" and "to" are missing + if opts["from"] == nil && opts["to"] == nil { + return nil + } + + r := dashv2alpha1.DashboardV2alpha1RangeMapOptions{} + if from, ok := opts["from"].(float64); ok { + r.From = &from + } + if to, ok := opts["to"].(float64); ok { + r.To = &to + } + + // Result is a DashboardValueMappingResult + if resMap, ok := opts["result"].(map[string]interface{}); ok { + r.Result = buildValueMappingResult(resMap) + } + + rangeMap.Options = r + return rangeMap +} + +func buildRegexMap(mappingMap map[string]interface{}) *dashv2alpha1.DashboardRegexMap { + regexMap := &dashv2alpha1.DashboardRegexMap{} + regexMap.Type = dashv2alpha1.DashboardMappingTypeRegex + + opts, ok := mappingMap["options"].([]interface{}) + if !ok || len(opts) == 0 { + return nil + } + + optMap, ok := opts[0].(map[string]interface{}) + if !ok { + return nil + } + + r := dashv2alpha1.DashboardV2alpha1RegexMapOptions{} + if pattern, ok := optMap["regex"].(string); ok { + r.Pattern = pattern + } + + // Result is a DashboardValueMappingResult + if resMap, ok := optMap["result"].(map[string]interface{}); ok { + r.Result = buildValueMappingResult(resMap) + } + + regexMap.Options = r + return regexMap +} + +func buildSpecialValueMap(mappingMap map[string]interface{}) *dashv2alpha1.DashboardSpecialValueMap { + specialMap := &dashv2alpha1.DashboardSpecialValueMap{} + specialMap.Type = dashv2alpha1.DashboardMappingTypeSpecial + + opts, ok := mappingMap["options"].(map[string]interface{}) + if !ok || len(opts) == 0 { + return nil + } + + r := dashv2alpha1.DashboardV2alpha1SpecialValueMapOptions{} + if match, ok := opts["match"].(string); ok { + r.Match = dashv2alpha1.DashboardSpecialValueMatch(match) + } + + // Result is a DashboardValueMappingResult + if resMap, ok := opts["result"].(map[string]interface{}); ok { + r.Result = buildValueMappingResult(resMap) + } + + specialMap.Options = r + return specialMap +} + +func buildValueMappingResult(resMap map[string]interface{}) dashv2alpha1.DashboardValueMappingResult { + res := dashv2alpha1.DashboardValueMappingResult{} + + if text, ok := resMap["text"].(string); ok { + res.Text = &text + } + if color, ok := resMap["color"].(string); ok { + res.Color = &color + } + if icon, ok := resMap["icon"].(string); ok { + res.Icon = &icon + } + if idx, ok := resMap["index"].(float64); ok { + idx32 := int32(idx) + res.Index = &idx32 + } + + return res +} + +func buildThresholdsConfig(thresholdsMap map[string]interface{}) *dashv2alpha1.DashboardThresholdsConfig { + thresholdsConfig := &dashv2alpha1.DashboardThresholdsConfig{} + + // Convert mode + if mode, ok := thresholdsMap["mode"].(string); ok { + thresholdsConfig.Mode = dashv2alpha1.DashboardThresholdsMode(mode) + } + + // Convert steps + if steps, ok := thresholdsMap["steps"].([]interface{}); ok { + thresholdsConfig.Steps = make([]dashv2alpha1.DashboardThreshold, 0, len(steps)) + for _, step := range steps { + if stepMap, ok := step.(map[string]interface{}); ok { + threshold := dashv2alpha1.DashboardThreshold{} + // Handle threshold value: null means -Infinity + if value, exists := stepMap["value"]; exists { + if value == nil { + // null means -Infinity + threshold.Value = nil + } else if floatVal, ok := value.(float64); ok { + threshold.Value = &floatVal + } + } else { + // Value not present, leave as nil (represents -Infinity) + threshold.Value = nil + } + if color, ok := stepMap["color"].(string); ok { + threshold.Color = color + } + thresholdsConfig.Steps = append(thresholdsConfig.Steps, threshold) + } + } + } + + return thresholdsConfig +} + +func extractFieldConfigOverrides(fieldConfig map[string]interface{}) []dashv2alpha1.DashboardV2alpha1FieldConfigSourceOverrides { + overrides, ok := fieldConfig["overrides"].([]interface{}) + if !ok || len(overrides) == 0 { + // Use empty array to match frontend behavior + return []dashv2alpha1.DashboardV2alpha1FieldConfigSourceOverrides{} + } + + result := make([]dashv2alpha1.DashboardV2alpha1FieldConfigSourceOverrides, 0, len(overrides)) + for _, override := range overrides { + overrideMap, ok := override.(map[string]interface{}) + if !ok { + continue + } + + fieldOverride := dashv2alpha1.DashboardV2alpha1FieldConfigSourceOverrides{} + + if systemRef, ok := overrideMap["__systemRef"].(string); ok { + fieldOverride.SystemRef = &systemRef + } + + // Map override fields individually + if matcher, exists := overrideMap["matcher"]; exists { + if matcherMap, ok := matcher.(map[string]interface{}); ok { + fieldOverride.Matcher = dashv2alpha1.DashboardMatcherConfig{ + Id: schemaversion.GetStringValue(matcherMap, "id"), + Options: matcherMap["options"], + } + } + } + if properties, exists := overrideMap["properties"]; exists { + if propertiesArray, ok := properties.([]interface{}); ok { + fieldOverride.Properties = make([]dashv2alpha1.DashboardDynamicConfigValue, 0, len(propertiesArray)) + for _, property := range propertiesArray { + if propertyMap, ok := property.(map[string]interface{}); ok { + fieldOverride.Properties = append(fieldOverride.Properties, dashv2alpha1.DashboardDynamicConfigValue{ + Id: schemaversion.GetStringValue(propertyMap, "id"), + Value: propertyMap["value"], + }) + } + } + } + } + + result = append(result, fieldOverride) + } + + return result +} diff --git a/apps/dashboard/pkg/migration/conversion/v2.go b/apps/dashboard/pkg/migration/conversion/v2.go index 91fcd8cf07a..db1824e725c 100644 --- a/apps/dashboard/pkg/migration/conversion/v2.go +++ b/apps/dashboard/pkg/migration/conversion/v2.go @@ -27,7 +27,7 @@ func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, s return nil } -func Convert_V2alpha1_to_V1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { +func Convert_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { out.ObjectMeta = in.ObjectMeta // TODO: implement V2 to V1 conversion @@ -89,7 +89,7 @@ func Convert_V2beta1_to_V0(in *dashv2beta1.Dashboard, out *dashv0.Dashboard, sco return nil } -func Convert_V2beta1_to_V1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { +func Convert_V2beta1_to_V1beta1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { out.ObjectMeta = in.ObjectMeta // TODO: implement v2beta1 to V1 conversion diff --git a/apps/dashboard/pkg/migration/conversion/v2_test.go b/apps/dashboard/pkg/migration/conversion/v2_test.go new file mode 100644 index 00000000000..fa3b947a2d0 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/v2_test.go @@ -0,0 +1,42 @@ +package conversion + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashv2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1" +) + +// TestV2alpha1ConversionErrorHandling tests that v2alpha1 conversion functions properly handle errors +func TestV2alpha1ConversionErrorHandling(t *testing.T) { + t.Run("Convert_V2alpha1_to_V2beta1 sets status correctly on success", func(t *testing.T) { + source := &dashv2alpha1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: dashv2alpha1.DashboardSpec{ + Title: "test dashboard", + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + }, + }, + } + target := &dashv2beta1.Dashboard{} + + err := Convert_V2alpha1_to_V2beta1(source, target, nil) + + require.NoError(t, err) + // Verify success status is set correctly + require.NotNil(t, target.Status.Conversion) + require.False(t, target.Status.Conversion.Failed) + require.Equal(t, dashv2alpha1.VERSION, *target.Status.Conversion.StoredVersion) + require.Nil(t, target.Status.Conversion.Error) + }) +} diff --git a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go index ec673c80137..9243d246cf0 100644 --- a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go +++ b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go @@ -125,22 +125,27 @@ func convertDataQuery_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardDataQueryKin if in == nil { // v2beta1 requires a query even if v2alpha1 had none, so create a default out.Kind = "DataQuery" - out.Group = "" out.Version = "v0" out.Spec = make(map[string]interface{}) + // Don't set group field for manual annotations (no datasource) } else { out.Kind = "DataQuery" - out.Group = in.Kind out.Version = "v0" out.Spec = in.Spec + // Don't set group field by default - only set if datasource is present } - // Convert datasource reference + // Convert datasource reference and set group from datasource type if datasource != nil { out.Datasource = &dashv2beta1.DashboardV2beta1DataQueryKindDatasource{} if datasource.Uid != nil { out.Datasource.Name = datasource.Uid } + // Set group to datasource type to match frontend behavior + // Only set group if datasource type is not empty (matches frontend logic) + if datasource.Type != nil && *datasource.Type != "" { + out.Group = *datasource.Type + } } return nil @@ -256,7 +261,7 @@ func convertQueryOptions_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardQueryOpti func convertVizConfig_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardVizConfigKind, out *dashv2beta1.DashboardVizConfigKind) error { out.Kind = "VizConfig" - out.Group = in.Kind + out.Group = in.Kind // in.Kind is the panel type (e.g., "timeseries", "graph") out.Version = in.Spec.PluginVersion out.Spec = dashv2beta1.DashboardVizConfigSpec{ Options: in.Spec.Options, @@ -303,6 +308,7 @@ func convertFieldConfig_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardFieldConfi Steps: make([]dashv2beta1.DashboardThreshold, len(in.Thresholds.Steps)), } for i, step := range in.Thresholds.Steps { + // Convert threshold values: preserve null values from v2alpha1 out.Thresholds.Steps[i] = dashv2beta1.DashboardThreshold{ Value: step.Value, Color: step.Color, @@ -329,6 +335,8 @@ func convertFieldConfig_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardFieldConfi } func convertFieldConfigOverride_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardV2alpha1FieldConfigSourceOverrides, out *dashv2beta1.DashboardV2beta1FieldConfigSourceOverrides) { + out.SystemRef = in.SystemRef + out.Matcher = dashv2beta1.DashboardMatcherConfig{ Id: in.Matcher.Id, Options: in.Matcher.Options, @@ -629,6 +637,7 @@ func convertVariable_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardVariableKind, } if in.IntervalVariableKind != nil { + defaultIntervalVariableSpec := dashv2beta1.NewDashboardIntervalVariableSpec() out.IntervalVariableKind = &dashv2beta1.DashboardIntervalVariableKind{ Kind: in.IntervalVariableKind.Kind, Spec: dashv2beta1.DashboardIntervalVariableSpec{ @@ -639,7 +648,7 @@ func convertVariable_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardVariableKind, Auto: in.IntervalVariableKind.Spec.Auto, AutoMin: in.IntervalVariableKind.Spec.AutoMin, AutoCount: in.IntervalVariableKind.Spec.AutoCount, - Refresh: dashv2beta1.DashboardVariableRefresh(in.IntervalVariableKind.Spec.Refresh), + Refresh: defaultIntervalVariableSpec.Refresh, Label: in.IntervalVariableKind.Spec.Label, Hide: dashv2beta1.DashboardVariableHide(in.IntervalVariableKind.Spec.Hide), SkipUrlSync: in.IntervalVariableKind.Spec.SkipUrlSync, @@ -690,11 +699,19 @@ func convertVariable_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardVariableKind, } if in.AdhocVariableKind != nil { + group := "" + var uid *string + if in.AdhocVariableKind.Spec.Datasource != nil { + if in.AdhocVariableKind.Spec.Datasource.Type != nil { + group = *in.AdhocVariableKind.Spec.Datasource.Type + } + uid = in.AdhocVariableKind.Spec.Datasource.Uid + } out.AdhocVariableKind = &dashv2beta1.DashboardAdhocVariableKind{ Kind: in.AdhocVariableKind.Kind, - Group: *in.AdhocVariableKind.Spec.Datasource.Type, + Group: group, Datasource: &dashv2beta1.DashboardV2beta1AdhocVariableKindDatasource{ - Name: in.AdhocVariableKind.Spec.Datasource.Uid, + Name: uid, }, Spec: dashv2beta1.DashboardAdhocVariableSpec{ Name: in.AdhocVariableKind.Spec.Name, diff --git a/apps/dashboard/pkg/migration/frontend_defaults.go b/apps/dashboard/pkg/migration/frontend_defaults.go index c376be6f47f..1df28c4de27 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults.go +++ b/apps/dashboard/pkg/migration/frontend_defaults.go @@ -440,10 +440,7 @@ func getPanels(dashboard map[string]interface{}) []map[string]interface{} { func cleanupPanelForSaveWithContext(panel map[string]interface{}, isNested bool) { // Apply auto-migration logic (matches frontend PanelModel constructor) // This happens during cleanup phase to match when frontend applies auto-migration - // Only apply auto-migration to top-level panels, not nested ones (matches frontend behavior) - if !isNested { - applyPanelAutoMigration(panel) - } + applyPanelAutoMigration(panel) // Library panel specific cleanup (matches frontend behavior) // Frontend only preserves id, title, gridPos, and libraryPanel for library panels diff --git a/apps/dashboard/pkg/migration/migrate.go b/apps/dashboard/pkg/migration/migrate.go index 08cba231fd3..9c887b553c6 100644 --- a/apps/dashboard/pkg/migration/migrate.go +++ b/apps/dashboard/pkg/migration/migrate.go @@ -13,11 +13,20 @@ func Initialize(dsInfoProvider schemaversion.DataSourceInfoProvider) { migratorInstance.init(dsInfoProvider) } +// GetDataSourceInfoProvider returns the datasource info provider instance that was initialized. +// This allows reuse of the same provider instance across migrations and conversions. +func GetDataSourceInfoProvider() schemaversion.DataSourceInfoProvider { + // Wait for initialization to complete + <-migratorInstance.ready + return migratorInstance.dsInfoProvider +} + // ResetForTesting resets the migrator singleton for testing purposes. func ResetForTesting() { migratorInstance = &migrator{ - migrations: map[int]schemaversion.SchemaVersionMigrationFunc{}, - ready: make(chan struct{}), + migrations: map[int]schemaversion.SchemaVersionMigrationFunc{}, + ready: make(chan struct{}), + dsInfoProvider: nil, } initOnce = sync.Once{} } @@ -37,12 +46,14 @@ var ( ) type migrator struct { - ready chan struct{} - migrations map[int]schemaversion.SchemaVersionMigrationFunc + ready chan struct{} + migrations map[int]schemaversion.SchemaVersionMigrationFunc + dsInfoProvider schemaversion.DataSourceInfoProvider } func (m *migrator) init(dsInfoProvider schemaversion.DataSourceInfoProvider) { initOnce.Do(func() { + m.dsInfoProvider = dsInfoProvider m.migrations = schemaversion.GetMigrations(dsInfoProvider) close(m.ready) }) diff --git a/apps/dashboard/pkg/migration/schemaversion/migration_utils.go b/apps/dashboard/pkg/migration/schemaversion/migration_utils.go index b3bd79d6fd4..64dfc61589e 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migration_utils.go +++ b/apps/dashboard/pkg/migration/schemaversion/migration_utils.go @@ -8,12 +8,15 @@ import ( // migration_utils.go contains shared utility functions used across multiple schema version migrations. // GetStringValue safely extracts a string value from a map, returning empty string if not found or not a string -func GetStringValue(m map[string]interface{}, key string) string { +func GetStringValue(m map[string]interface{}, key string, defaultValue ...string) string { if value, ok := m[key]; ok { if s, ok := value.(string); ok { return s } } + if len(defaultValue) > 0 { + return defaultValue[0] + } return "" } diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/annotations/multi-lane-annotations.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/annotations/multi-lane-annotations.v42.json new file mode 100644 index 00000000000..f77cb68c01f --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/annotations/multi-lane-annotations.v42.json @@ -0,0 +1,1024 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "maxDataPoints": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "noise": 10, + "refId": "A", + "scenarioId": "random_walk", + "spread": 10 + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1720697881000,\n 1728580067000,\n 1730129182000,\n 1730734644000,\n 1732542118000,\n 1736426576000,\n 1736874473000,\n 1738676647000,\n 1740067713000,\n 1740684246000,\n 1743733609000,\n 1744034815000,\n 1745335229000,\n 1745936130000,\n 1746107356000,\n 1747055303000,\n 1747946736000,\n 1748446531000,\n 1750098297000,\n 1750963080000,\n 1750963096000,\n 1752067555000,\n 1752080606000,\n 1753125884000,\n 1754395568000,\n 1754407010000,\n 1756228450000,\n 1757947221000,\n 1759763050000,\n 1759924632000,\n 1761572602000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"timeEnd\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1729081505000,\n 1730129307000,\n 1730838127000,\n 1732543887000,\n 1736528756000,\n 1736874600000,\n 1740064375000,\n 1740170732000,\n 1742397235000,\n 1743733611000,\n 1744038997000,\n 1745341972000,\n 1745936141000,\n 1746118905000,\n 1747055307000,\n 1747853613000,\n 1748446520000,\n 1750187872000,\n 1750963071000,\n 1751038503000,\n 1752067563000,\n 1752166243000,\n 1753396603000,\n 1754395592000,\n 1754407004000,\n 1756228445000,\n 1758134868000,\n 1759763044000,\n 1761572592000,\n 1761572593000,\n null\n ],\n \"type\": \"number\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"0.1.0\",\n \"1.0.2\",\n \"1.0.3\",\n \"1.0.4\",\n \"1.0.5\",\n \"1.0.6\",\n \"1.0.7\",\n \"1.0.8\",\n \"1.0.9\",\n \"1.0.10\",\n \"1.0.11\",\n \"1.0.12\",\n \"1.0.13\",\n \"1.0.14\",\n \"1.0.15\",\n \"1.0.16\",\n \"1.0.17\",\n \"1.0.18\",\n \"1.0.19\",\n \"1.0.20\",\n \"1.0.21\",\n \"1.0.22\",\n \"1.0.23\",\n \"1.0.24\",\n \"1.0.25\",\n \"1.0.26\",\n \"1.0.27\",\n \"1.0.28\",\n \"1.0.29\",\n \"1.0.30\",\n \"1.0.31\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n false\n ],\n \"type\": \"boolean\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"red\",\n \"mappings\": {\n \"text\": {\n \"source\": \"field\",\n \"value\": \"closed\"\n },\n \"time\": {\n \"source\": \"field\",\n \"value\": \"created_at\"\n },\n \"timeEnd\": {\n \"source\": \"field\",\n \"value\": \"closed_at\"\n },\n \"title\": {\n \"source\": \"field\",\n \"value\": \"title\"\n }\n },\n \"name\": \"Milestones\",\n \"target\": {\n \"options\": {\n \"query\": \"\"\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Milestones\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n false\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 31,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n },\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Issues\",\n \"Issues\",\n \"Issues\",\n \"Issues\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#FADE2A\",\n \"#FADE2A\",\n \"#FADE2A\",\n \"#FADE2A\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760112753000,\n 1759930212000,\n 1759924899000,\n 1759850404000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"timeEnd\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760972421000,\n null,\n null,\n 1759926839000\n ],\n \"type\": \"number\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"[BUG]: Error when data source configured for multi tenant queries\",\n \"[FEAT]: Allow users without explore permissions to use logs drilldown\",\n \"Sync Logs panel and label/fields empty states\",\n \"fix: labels clear variable state not showing\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"[BUG]: Error when data source configured for multi tenant queries\",\n \"[FEAT]: Allow users without explore permissions to use logs drilldown\",\n \"Sync Logs panel and label/fields empty states\",\n \"fix: labels clear variable state not showing\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"tags\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n [\n \"bug\",\n \"needs-triage\"\n ],\n [\n \"enhancement\",\n \"needs-triage\"\n ],\n [\n \"needs-triage\"\n ],\n [\n \"bug\"\n ]\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"yellow\",\n \"mappings\": {\n \"tags\": {\n \"source\": \"field\",\n \"value\": \"labels\"\n },\n \"time\": {\n \"source\": \"field\",\n \"value\": \"created_at\"\n },\n \"timeEnd\": {\n \"source\": \"field\",\n \"value\": \"closed_at\"\n }\n },\n \"name\": \"Issues\",\n \"target\": {\n \"options\": {\n \"query\": \"\"\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Issues\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n false,\n false,\n true\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 4,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n },\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Pulls\",\n \"Pulls\",\n \"Pulls\",\n \"Pulls\",\n \"Pulls\",\n \"Pulls\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1759941908000,\n 1759854730000,\n 1759852733000,\n 1759841416000,\n 1759786844000,\n 1759762979000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"timeEnd\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760110687000,\n 1759857566000,\n 1759926838000,\n 1759843529000,\n 1759848237000,\n 1759769543000\n ],\n \"type\": \"number\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"feat(EmptyLogs): add button to fix with assistant\",\n \"docs: Update troubleshooting page\",\n \"fix: unexpected clear variable behavior\",\n \"docs: update stale readme, fix docker install script\",\n \"docs: Update install and troubleshooting\",\n \"fix: fix RegExp.source removing flags, use toString instead\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"feat(EmptyLogs): add button to fix with assistant\",\n \"docs: Update troubleshooting page\",\n \"fix: unexpected clear variable behavior\",\n \"docs: update stale readme, fix docker install script\",\n \"docs: Update install and troubleshooting\",\n \"fix: fix RegExp.source removing flags, use toString instead\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"blue\",\n \"mappings\": {\n \"time\": {\n \"source\": \"field\",\n \"value\": \"created_at\"\n },\n \"timeEnd\": {\n \"source\": \"field\",\n \"value\": \"merged_at\"\n }\n },\n \"name\": \"Pulls\",\n \"target\": {\n \"options\": {\n \"query\": \"\",\n \"timeField\": 1\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Pull_Requests\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n true,\n true,\n true,\n true,\n true\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 6,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n },\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760349982000,\n 1760110687000,\n 1759950041000,\n 1759948586000,\n 1759933371000,\n 1759926838000,\n 1759857566000,\n 1759850172000,\n 1759848237000,\n 1759843529000,\n 1759769543000,\n 1759762725000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Matias Chomicki\",\n \"Matias Chomicki\",\n \"github-actions[bot]\",\n \"Liza Detrick\",\n \"grafana-plugins-platform-bot[bot]\",\n \"Galen Kistler\",\n \"J Stickler\",\n \"Piotr Jamróz\",\n \"J Stickler\",\n \"Galen Kistler\",\n \"Galen Kistler\",\n \"Galen Kistler\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"feat(LogsListScene): add defaultDisplayedFields support (#1554)\\n\\n* feat(LogsListScene): add defaultDisplayedFields support\\n\\n* chore: update mock\\n\\n* fix(Table): omit unsupported field\\n\\n* fix(LogListScene): reset default displayed fields when switching viz\\n\\n* chore: lint\",\n \"feat(EmptyLogs): add button to fix with assistant (#1571)\\n\\n* feat(LogListScene): add error type to identify empty results\\n\\n* feat(LogsPanelError): use assistant to investigate no results\\n\\n* fix(Shoo): shoo\\n\\n* feat(EmbeddedLogs): customize empty logs prompt\\n\\n* feat(LogsPanelError): implement custom CTA\\n\\n* chore: spelling\\n\\n* chore: add unit test\\n\\n* chore: remove it.only\\n\\n* fix(variableHelpers): exclude primary label from variables to clear\\n\\n* feat(embedding): refactor options\\n\\n* feat(assistant): create service\\n\\n* feat(assistant): integrate with empty layout and no matching labels\\n\\n* chore: fix types\\n\\n* chore: update tests\\n\\n* chore: remove quotes\\n\\n* chore: prevent access to an undefined object\\n\\n* chore: move label to constants\\n\\n* Revert \\\"chore: move label to constants\\\"\\n\\nThis reverts commit fbdf96415e5b2af7a5cae4eec6e6d5a30bc05d1c.\\n\\n* chore: use name instead of label\\n\\n* Revert \\\"fix(Shoo): shoo\\\"\\n\\nThis reverts commit 75993f7a845b514cb5da9667e94a2f0a3c913f24.\\n\\n* chore: more unit tests\\n\\n* test(variableHelpers): test getVariablesThatCanBeCleared\",\n \"chore: bump @grafana/create-plugin configuration to 5.26.9 (#1559)\",\n \"feat(table): preferences (#1534)\",\n \"chore(version): bump version to v1.0.29\",\n \"fix: unexpected clear variable behavior (#1567)\\n\\n* fix: show clear ui when query contains clearable label filters\",\n \"docs: Update troubleshooting page (#1568)\",\n \"fix: validate primary label correctly (#1561)\\n\\n* fix: properly clear variables for legacy urls\",\n \"docs: Update install and troubleshooting (#1564)\\n\\nCo-authored-by: Galen Kistler \u003c109082771+gtk-grafana@users.noreply.github.com\u003e\",\n \"docs: update stale readme, fix docker install script (#1565)\\n\\n* docs: update stale readme\\n\\n* chore: fix docker script\",\n \"fix: fix RegExp.source removing flags, use toString instead (#1563)\\n\\n* fix: fix RegExp.source removing flags, use toString instead\",\n \"fix: stale urls (#1562)\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"id\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"cf08043276597abe16f4ae9dbe97bd988d31b7ed\",\n \"01e343dacde3b9fcd862ec2cb2b04fbea4eb6d63\",\n \"9e67734938b980a80496d4cdc60efe7dbc4ba74e\",\n \"924ebd235b6ea99d297d8e04e08ac8ea90a32968\",\n \"23b9b5dc0504a4c397b326562cb796cfdc0531df\",\n \"089fdfa26c7daad5543be1aae2377850348663c3\",\n \"437a568a7a7279c436a674c7b1b0d11a4971d47b\",\n \"ee12a20b32db26b82fe32ed9d5868bd2dde4a4d0\",\n \"657880b399682101616b3413e04c2b664d018ef3\",\n \"fc776d767ebf2a64fb9fad7ab0f479cdaf536cdb\",\n \"a9207d235af9b2f5a4915f072801128a41c4063b\",\n \"e2eae8b8f45dc685659689f85962b0f0a58f13b1\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"green\",\n \"mappings\": {\n \"text\": {\n \"source\": \"field\",\n \"value\": \"message\"\n },\n \"time\": {\n \"source\": \"field\",\n \"value\": \"committed_at\"\n },\n \"title\": {\n \"source\": \"field\",\n \"value\": \"author\"\n }\n },\n \"name\": \"Commits\",\n \"target\": {\n \"options\": {\n \"gitRef\": \"main\"\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Commits\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 12,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n }\n]", + "refId": "B", + "scenarioId": "raw_frame" + } + ], + "title": "Time series", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "annotations": { + "multiLane": true + }, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Time series (multi-lane)", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "candleStyle": "candles", + "colorStrategy": "open-close", + "colors": { + "down": "red", + "up": "green" + }, + "includeAllFields": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mode": "candles+volume", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Candlestick", + "type": "candlestick" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "annotations": { + "multiLane": true + }, + "candleStyle": "candles", + "colorStrategy": "open-close", + "colors": { + "down": "red", + "up": "green" + }, + "includeAllFields": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mode": "candles+volume", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Candlestick (multi-lane)", + "type": "candlestick" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "State timeline", + "type": "state-timeline" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 6, + "options": { + "alignValue": "left", + "annotations": { + "multiLane": true + }, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "State timeline (multi-lane)", + "type": "state-timeline" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 7, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Heatmap", + "type": "heatmap" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 8, + "options": { + "annotations": { + "multiLane": true + }, + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Heatmap (multi-lane)", + "type": "heatmap" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 9, + "options": { + "colWidth": 0.9, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Status history", + "type": "status-history" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 10, + "options": { + "annotations": { + "multiLane": true + }, + "colWidth": 0.9, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Status history (multi-lane)", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 40 + }, + "id": 11, + "maxDataPoints": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"type\": \"timeseries\"\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"config\": {\n \"interval\": 604800000\n }\n },\n {\n \"name\": \"A-series\",\n \"type\": \"number\",\n \"labels\": {},\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1755870318505,\n 1756475118505,\n 1757079918505,\n 1757684718505,\n 1758289518505,\n 1758894318505,\n 1759499118505,\n 1760103918505,\n 1760708718505,\n 1761313518505\n ],\n [\n 49.57457814271496,\n 64.78808691382616,\n 94.88860442042386,\n 96.59132232810856,\n 58.57144477681538,\n 79.33618638515327,\n 89.64117713117561,\n 134.51905322565585,\n 122.83710544843791,\n 79.84039369237018\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"B\",\n \"name\": \"xymark\",\n \"meta\": {\n \"dataTopic\": \"annotations\"\n },\n \"fields\": [\n {\n \"name\": \"xMin\",\n \"type\": \"time\",\n \"config\": {}\n },\n {\n \"name\": \"xMax\",\n \"type\": \"time\",\n \"config\": {}\n },\n {\n \"name\": \"yMin\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"yMax\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"color\",\n \"type\": \"string\",\n \"config\": {}\n },\n {\n \"name\": \"fillOpacity\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"lineWidth\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"lineStyle\",\n \"type\": \"string\",\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1757684718505\n ],\n [\n 1758894318505\n ],\n [\n 70\n ],\n [\n 120\n ],\n [\n \"#f00\"\n ],\n [\n 0.1\n ],\n [\n 1\n ],\n [\n \"dash\"\n ]\n ]\n }\n }\n]", + "refId": "B", + "scenarioId": "raw_frame" + } + ], + "title": "xymark", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "tags": [ + "gdev", + "panel-tests", + "graph-ng" + ], + "templating": { + "list": [] + }, + "time": { + "from": "2025-08-22T13:45:18.505Z", + "to": "2025-10-28T04:38:37.130Z" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Multi-lane annotations", + "uid": "ad7p5pj", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json index 2c63e2eb953..863d7b2a102 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json @@ -2147,4 +2147,4 @@ "title": "Panel tests - Gauge (new)", "uid": "panel-tests-gauge-new", "weekStart": "" -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json index ba341da51ca..a30af0d8fe0 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json @@ -384,4 +384,4 @@ "title": "Heatmap X axis", "uid": "5Y0jv6pVz", "weekStart": "" -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-status-history/status-history-thresholds-mappings.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-status-history/status-history-thresholds-mappings.v42.json new file mode 100644 index 00000000000..edce1004bb1 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-status-history/status-history-thresholds-mappings.v42.json @@ -0,0 +1,1163 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 15116, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 11, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "title": "default", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 10 + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "#6ED0E0", + "value": 30 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 2, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "default absolute thresholds", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 10 + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "#6ED0E0", + "value": 30 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 8, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "default percentage thresholds", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 10 + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "#6ED0E0", + "value": 30 + } + ] + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 3, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "override thresholds", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [ + { + "options": { + "from": 0, + "result": { + "color": "green", + "index": 0 + }, + "to": 9.9999 + }, + "type": "range" + }, + { + "options": { + "from": 10, + "result": { + "color": "yellow", + "index": 1 + }, + "to": 14.9999 + }, + "type": "range" + }, + { + "options": { + "from": 15, + "result": { + "color": "red", + "index": 2 + }, + "to": 24.9999 + }, + "type": "range" + }, + { + "options": { + "from": 25, + "result": { + "color": "blue", + "index": 3 + }, + "to": 100000 + }, + "type": "range" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 6, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.47, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "default value mappings", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "from": 0, + "result": { + "color": "green", + "index": 0 + }, + "to": 9.9999 + }, + "type": "range" + }, + { + "options": { + "from": 10, + "result": { + "color": "yellow", + "index": 1 + }, + "to": 14.9999 + }, + "type": "range" + }, + { + "options": { + "from": 15, + "result": { + "color": "red", + "index": 2 + }, + "to": 24.9999 + }, + "type": "range" + }, + { + "options": { + "from": 25, + "result": { + "color": "blue", + "index": 3 + }, + "to": 100000 + }, + "type": "range" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 7, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "override value mappings", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 5, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 10\n },\n {\n \"color\": \"red\",\n \"value\": 15\n },\n {\n \"color\": \"#6ED0E0\",\n \"value\": 25\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000\n ],\n [\n 5,\n 10,\n 20,\n 30\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "field thresholds from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 9 + }, + "id": 9, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "hide": false, + "max": 30, + "min": 0.01, + "noise": 30, + "refId": "B", + "scenarioId": "random_walk", + "startValue": 1 + } + ], + "title": "threshold from random walk", + "transformations": [ + { + "id": "configFromData", + "options": { + "configRefId": "B", + "mappings": [ + { + "fieldName": "B-series", + "handlerKey": "threshold1" + } + ] + } + } + ], + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "color": "purple", + "index": 0, + "text": "null" + } + }, + "type": "special" + }, + { + "options": { + "match": "nan", + "result": { + "color": "red", + "index": 1, + "text": "NaN" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 18 + }, + "id": 12, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000\n ],\n [\n 5,\n null,\n 20,\n null,\n 40\n ]\n ],\n \"entities\": [null, { \"NaN\": [3]}]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "special null | NaN value mapping from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "match": "null+nan", + "result": { + "color": "super-light-red", + "index": 0, + "text": "null + NaN" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 18 + }, + "id": 13, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000\n ],\n [\n 5,\n null,\n 20,\n null,\n 40\n ]\n ],\n \"entities\": [null, { \"NaN\": [1]}]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "special null + NaN value mapping from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "match": "false", + "result": { + "color": "red", + "index": 0 + } + }, + "type": "special" + }, + { + "options": { + "match": "null+nan", + "result": { + "color": "blue", + "index": 1, + "text": "null + NaN" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 18 + }, + "id": 14, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000,\n 1674750835000,\n 1674754235000,\n 1674757835000\n ],\n [\n null,\n null,\n false,\n true,\n true,\n false,\n true,\n null\n ]\n ],\n \"entities\": [null, { \"NaN\": [0], \"Undefined\": [1]}]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "boolean values from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 18 + }, + "id": 15, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 0 + } + ], + "title": "no data", + "type": "status-history" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "tags": [ + "gdev", + "panel-tests", + "state-timeline", + "graph-ng" + ], + "templating": { + "list": [] + }, + "time": { + "from": "2023-01-26T11:29:47.180Z", + "to": "2023-01-26T16:29:39.205Z" + }, + "timepicker": {}, + "timezone": "utc", + "title": "StatusHistory - Thresholds \u0026 Mappings", + "uid": "a2f4ad9e-3b44-4624-8067-35f31be5d309", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json index dae0d524c23..a0fae31696a 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json @@ -183,6 +183,24 @@ }, "fieldConfig": { "defaults": { + "actions": [ + { + "fetch": { + "body": "{}", + "headers": [ + [ + "Content-Type", + "application/json" + ] + ], + "method": "GET", + "queryParams": [], + "url": "/api/health" + }, + "title": "Get instance health", + "type": "fetch" + } + ], "color": { "fixedColor": "blue", "mode": "shades" @@ -195,6 +213,13 @@ }, "inspect": false }, + "links": [ + { + "targetBlank": true, + "title": "Google Grafana", + "url": "https://google.com/search?q=grafana" + } + ], "mappings": [], "thresholds": { "mode": "absolute", @@ -208,27 +233,7 @@ "value": 80 } ] - }, - "actions": [ - { - "fetch": { - "body": "{}", - "headers": [["Content-Type", "application/json"]], - "method": "GET", - "queryParams": [], - "url": "/api/health" - }, - "title": "Get instance health", - "type": "fetch" - } - ], - "links": [ - { - "targetBlank": true, - "title": "Google Grafana", - "url": "https://google.com/search?q=grafana" - } - ] + } }, "overrides": [] }, @@ -612,4 +617,4 @@ "title": "Panel Tests - Table - Sparklines", "uid": "d6373b49-1957-4f00-9218-ee2120d3ecd9", "weekStart": "" -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json index f36c1de1710..1d5386a6b17 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json @@ -1071,32 +1071,27 @@ "type": "state-timeline" }, { - "id": 15, - "type": "state-timeline", - "title": "no data", - "gridPos": { - "x": 18, - "y": 18, - "h": 9, - "w": 6 + "datasource": { + "type": "grafana-testdata-datasource" }, "fieldConfig": { "defaults": { - "custom": { - "lineWidth": 0, - "fillOpacity": 70, - "spanNulls": false, - "insertNulls": false, - "hideFrom": { - "tooltip": false, - "viz": false, - "legend": false - }, - "axisPlacement": "auto" - }, "color": { "mode": "palette-classic" }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, "mappings": [], "thresholds": { "mode": "absolute", @@ -1105,38 +1100,43 @@ "color": "green" } ] - }, - "fieldMinMax": false + } }, "overrides": [] }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 18 + }, + "id": 15, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, "pluginVersion": "12.3.0-pre", "targets": [ { - "scenarioId": "random_walk", "refId": "A", + "scenarioId": "random_walk", "seriesCount": 0 } ], - "datasource": { - "type": "grafana-testdata-datasource" - }, - "options": { - "mergeValues": true, - "showValue": "auto", - "alignValue": "center", - "rowHeight": 0.9, - "legend": { - "showLegend": true, - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none", - "hideZeros": false - } - } + "title": "no data", + "type": "state-timeline" } ], "preload": false, @@ -1160,4 +1160,4 @@ "title": "StateTimeline - Thresholds \u0026 Mappings", "uid": "Kce7z9TVz", "weekStart": "" -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v11.no-op-migration.json b/apps/dashboard/pkg/migration/testdata/input/v11.no-op-migration.json index 82b054d9d11..7eabaf6bc5d 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v11.no-op-migration.json +++ b/apps/dashboard/pkg/migration/testdata/input/v11.no-op-migration.json @@ -40,13 +40,7 @@ ] }, "annotations": { - "list": [ - { - "name": "Annotations & Alerts", - "datasource": "grafana", - "enable": true - } - ] + "list": [] }, "time": { "from": "now-6h", diff --git a/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json b/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json index e16dca0e87a..df3a62e6dae 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json +++ b/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json @@ -43,7 +43,10 @@ "list": [ { "name": "Annotations & Alerts", - "datasource": "grafana", + "datasource": { + "type": "grafana", + "uid": "grafana" + }, "enable": true } ] diff --git a/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json b/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json index b3d70bd1ac9..fbb88e23546 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json +++ b/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json @@ -110,7 +110,9 @@ "enable": true, "iconColor": "purple", "datasource": { - "uid": "prometheus" + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" } } ] diff --git a/apps/dashboard/pkg/migration/testdata/input/v36.ds_name_to_ref.json b/apps/dashboard/pkg/migration/testdata/input/v36.ds_name_to_ref.json index 0bfb9401d7a..30fb89d4ad0 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v36.ds_name_to_ref.json +++ b/apps/dashboard/pkg/migration/testdata/input/v36.ds_name_to_ref.json @@ -34,6 +34,8 @@ { "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID", "datasource": { + "type": "prometheus", + "apiVersion": "v1", "uid": "unknown-datasource-name" } } @@ -119,7 +121,9 @@ { "refId": "B", "datasource": { - "uid": "existing-target-uid" + "uid": "existing-target-uid", + "type": "elasticsearch", + "apiVersion": "v2" } } ] @@ -137,7 +141,8 @@ "refId": "A", "datasource": { "uid": "existing-target-uid", - "type": "elasticsearch" + "type": "elasticsearch", + "apiVersion": "v2" } } ] @@ -167,7 +172,9 @@ { "refId": "A", "datasource": { - "uid": "existing-target-uid" + "uid": "existing-target-uid", + "type": "elasticsearch", + "apiVersion": "v2" } }, { @@ -188,7 +195,9 @@ { "refId": "A", "datasource": { - "uid": "existing-target-uid" + "uid": "existing-target-uid", + "type": "elasticsearch", + "apiVersion": "v2" } } ] @@ -257,7 +266,9 @@ { "refId": "A", "datasource": { - "uid": "existing-target-uid" + "uid": "existing-target-uid", + "type": "elasticsearch", + "apiVersion": "v2" } } ] diff --git a/apps/dashboard/pkg/migration/testdata/input/v42.harky_must.json b/apps/dashboard/pkg/migration/testdata/input/v42.harky_must.json new file mode 100644 index 00000000000..440790150dc --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v42.harky_must.json @@ -0,0 +1,31 @@ +{ + "title": "v42 Migration Test - Harky Must", + "schemaVersion": 41, + "panels": [ + { + "id": 1, + "title": "Panel with hideFrom.viz = true", + "type": "timeseries", + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "viz": true + } + } + ] + } + ] + } + } + ] + } + \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v42.hidefrom_tooltip.json b/apps/dashboard/pkg/migration/testdata/input/v42.hidefrom_tooltip.json index cb7f1ed0027..428b6792464 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v42.hidefrom_tooltip.json +++ b/apps/dashboard/pkg/migration/testdata/input/v42.hidefrom_tooltip.json @@ -49,14 +49,22 @@ ] }, { + "__systemRef": "hideSeriesFrom", "matcher": { - "id": "__systemRef", - "options": "hiddenSeries" + "id": "byNames", + "options": { + "mode": "exclude", + "names": ["foo"], + "prefix": "All except:", + "readOnly": true + } }, "properties": [ { "id": "custom.hideFrom", "value": { + "legend": false, + "tooltip": false, "viz": true } } @@ -103,6 +111,10 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byName", + "options": "Time" + }, "properties": [ { "id": "unit", @@ -123,13 +135,18 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, "properties": [ { - "id": "custom.hideFrom", - "value": { - "viz": false, - "tooltip": false - } + "id": "unit", + "value": "short" } ] } @@ -144,12 +161,17 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, "properties": [ { "id": "custom.hideFrom", "value": { - "viz": true, - "tooltip": false + "legend": false, + "tooltip": false, + "viz": true } } ] diff --git a/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json b/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json index 73c1db88803..91b7af7d38e 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json +++ b/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json @@ -12,14 +12,20 @@ "annotations": [ { "name": "deployment", - "datasource": "prometheus", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus" + }, "enable": true, "iconColor": "red", "query": "ALERTS{alertname=\"DeploymentStarted\"}" }, { "name": "alerts", - "datasource": "loki", + "datasource": { + "uid": "loki-uid", + "type": "loki" + }, "enable": false, "iconColor": "yellow", "query": "{job=\"alertmanager\"}" @@ -32,12 +38,14 @@ { "name": "environment", "type": "filter", - "allFormat": null + "allFormat": null, + "query": "label_values(up, instance)" }, { "name": "service", "datasource": "prometheus", - "allFormat": "glob" + "allFormat": "glob", + "query": "label_values(up, instance)" }, { "name": "region", diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v11.no-op-migration.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v11.no-op-migration.v42.json index cde0f37b8a3..656752028b4 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v11.no-op-migration.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v11.no-op-migration.v42.json @@ -12,13 +12,6 @@ "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations \u0026 Alerts", "type": "dashboard" - }, - { - "datasource": { - "uid": "grafana" - }, - "enable": true, - "name": "Annotations \u0026 Alerts" } ] }, diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.no-op-migration.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.no-op-migration.v42.json index 1cda544d3fd..e9eaff02aa7 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.no-op-migration.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.no-op-migration.v42.json @@ -15,6 +15,7 @@ }, { "datasource": { + "type": "grafana", "uid": "grafana" }, "enable": true, diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v16.grid_layout_upgrade.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v16.grid_layout_upgrade.v42.json index e9d3b12001d..81f12085db0 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v16.grid_layout_upgrade.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v16.grid_layout_upgrade.v42.json @@ -128,6 +128,7 @@ "type": "table" }, { + "autoMigrateFrom": "graph", "gridPos": { "h": 6, "w": 12, @@ -137,9 +138,10 @@ "height": 200, "id": 4, "title": "Network I/O", - "type": "graph" + "type": "timeseries" }, { + "autoMigrateFrom": "graph", "gridPos": { "h": 6, "w": 12, @@ -149,7 +151,7 @@ "height": 200, "id": 5, "title": "Disk I/O", - "type": "graph" + "type": "timeseries" } ], "targets": [ diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v27.repeated_panels_and_constant_variable.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v27.repeated_panels_and_constant_variable.v42.json index 640e4c32e57..5cae702fdad 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v27.repeated_panels_and_constant_variable.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v27.repeated_panels_and_constant_variable.v42.json @@ -50,9 +50,10 @@ "id": 4, "panels": [ { + "autoMigrateFrom": "graph", "id": 6, "title": "Normal nested panel", - "type": "graph" + "type": "timeseries" } ], "targets": [ diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json index 12572e9d9b9..5c4596442f6 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json @@ -115,7 +115,9 @@ }, { "datasource": { - "uid": "prometheus" + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" }, "enable": true, "iconColor": "purple", diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v36.ds_name_to_ref.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v36.ds_name_to_ref.v42.json index 30eb032bb19..6b68231755f 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v36.ds_name_to_ref.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v36.ds_name_to_ref.v42.json @@ -47,6 +47,8 @@ }, { "datasource": { + "apiVersion": "v1", + "type": "prometheus", "uid": "unknown-datasource-name" }, "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" @@ -135,6 +137,8 @@ }, { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "B" @@ -152,6 +156,7 @@ "targets": [ { "datasource": { + "apiVersion": "v2", "type": "elasticsearch", "uid": "existing-target-uid" }, @@ -178,6 +183,8 @@ }, { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "description": "Tests panel with expression query - should not inherit expression as panel datasource", @@ -185,6 +192,8 @@ "targets": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "A" @@ -201,6 +210,8 @@ }, { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "description": "Tests panel inheriting datasource from target when panel datasource was default", @@ -208,6 +219,8 @@ "targets": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "A" @@ -290,6 +303,8 @@ "panels": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "description": "Nested panel in collapsed row with default datasource", @@ -297,6 +312,8 @@ "targets": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "A" diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v37.legend_normalization.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v37.legend_normalization.v42.json index 4e0e262a1c1..00db730ff0f 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v37.legend_normalization.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v37.legend_normalization.v42.json @@ -117,6 +117,7 @@ "type": "timeseries" }, { + "autoMigrateFrom": "graph", "id": 12, "options": { "legend": { @@ -125,7 +126,7 @@ } }, "title": "Nested Panel with Hidden DisplayMode", - "type": "graph" + "type": "timeseries" }, { "id": 13, diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.harky_must.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.harky_must.v42.json new file mode 100644 index 00000000000..7bf27227b56 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.harky_must.v42.json @@ -0,0 +1,62 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 1, + "title": "Panel with hideFrom.viz = true", + "type": "timeseries" + } + ], + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "v42 Migration Test - Harky Must", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.hidefrom_tooltip.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.hidefrom_tooltip.v42.json index 8ef37e2b890..1da5978b1a7 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.hidefrom_tooltip.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v42.hidefrom_tooltip.v42.json @@ -67,14 +67,23 @@ ] }, { + "__systemRef": "hideSeriesFrom", "matcher": { - "id": "__systemRef", - "options": "hiddenSeries" + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "foo" + ], + "prefix": "All except:", + "readOnly": true + } }, "properties": [ { "id": "custom.hideFrom", "value": { + "legend": false, "tooltip": true, "viz": true } @@ -121,6 +130,10 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byName", + "options": "Time" + }, "properties": [ { "id": "unit", @@ -143,13 +156,18 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, "properties": [ { - "id": "custom.hideFrom", - "value": { - "tooltip": false, - "viz": false - } + "id": "unit", + "value": "short" } ] } @@ -164,10 +182,15 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, "properties": [ { "id": "custom.hideFrom", "value": { + "legend": false, "tooltip": true, "viz": true } diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json index 1a18f9b01c2..ed1c43eafa9 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json @@ -15,7 +15,8 @@ }, { "datasource": { - "uid": "prometheus" + "type": "prometheus", + "uid": "default-ds-uid" }, "enable": true, "iconColor": "red", @@ -24,7 +25,8 @@ }, { "datasource": { - "uid": "loki" + "type": "loki", + "uid": "loki-uid" }, "enable": false, "iconColor": "yellow", @@ -108,6 +110,7 @@ }, "name": "environment", "options": [], + "query": "label_values(up, instance)", "refresh": 1, "type": "query" }, @@ -116,6 +119,7 @@ "datasource": "prometheus", "name": "service", "options": [], + "query": "label_values(up, instance)", "refresh": 1, "type": "query" }, diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v11.no-op-migration.v11.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v11.no-op-migration.v11.json index 7e6680140e0..74389483d79 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v11.no-op-migration.v11.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v11.no-op-migration.v11.json @@ -12,11 +12,6 @@ "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations \u0026 Alerts", "type": "dashboard" - }, - { - "datasource": "grafana", - "enable": true, - "name": "Annotations \u0026 Alerts" } ] }, diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v15.no-op-migration.v15.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v15.no-op-migration.v15.json index 39c7811cf90..d66d7d9aed4 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v15.no-op-migration.v15.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v15.no-op-migration.v15.json @@ -14,7 +14,10 @@ "type": "dashboard" }, { - "datasource": "grafana", + "datasource": { + "type": "grafana", + "uid": "grafana" + }, "enable": true, "name": "Annotations \u0026 Alerts" } diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v16.grid_layout_upgrade.v16.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v16.grid_layout_upgrade.v16.json index f5d6facd9b1..ec62492fb6c 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v16.grid_layout_upgrade.v16.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v16.grid_layout_upgrade.v16.json @@ -78,6 +78,7 @@ "type": "table" }, { + "autoMigrateFrom": "graph", "gridPos": { "h": 6, "w": 12, @@ -87,9 +88,10 @@ "height": 200, "id": 4, "title": "Network I/O", - "type": "graph" + "type": "timeseries" }, { + "autoMigrateFrom": "graph", "gridPos": { "h": 6, "w": 12, @@ -99,7 +101,7 @@ "height": 200, "id": 5, "title": "Disk I/O", - "type": "graph" + "type": "timeseries" } ], "title": "Collapsed Row", diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v27.repeated_panels_and_constant_variable.v27.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v27.repeated_panels_and_constant_variable.v27.json index 40cde403989..9a93c6b3dbf 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v27.repeated_panels_and_constant_variable.v27.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v27.repeated_panels_and_constant_variable.v27.json @@ -30,9 +30,10 @@ "id": 4, "panels": [ { + "autoMigrateFrom": "graph", "id": 6, "title": "Normal nested panel", - "type": "graph" + "type": "timeseries" } ], "title": "Row with repeated panels", diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json index 85a9932902b..9da77468be1 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json @@ -115,7 +115,9 @@ }, { "datasource": { - "uid": "prometheus" + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" }, "enable": true, "iconColor": "purple", diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v36.ds_name_to_ref.v36.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v36.ds_name_to_ref.v36.json index 3e5f592670f..26d7de2f594 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v36.ds_name_to_ref.v36.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v36.ds_name_to_ref.v36.json @@ -47,6 +47,8 @@ }, { "datasource": { + "apiVersion": "v1", + "type": "prometheus", "uid": "unknown-datasource-name" }, "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" @@ -135,6 +137,8 @@ }, { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "B" @@ -152,6 +156,7 @@ "targets": [ { "datasource": { + "apiVersion": "v2", "type": "elasticsearch", "uid": "existing-target-uid" }, @@ -178,6 +183,8 @@ }, { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "description": "Tests panel with expression query - should not inherit expression as panel datasource", @@ -185,6 +192,8 @@ "targets": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "A" @@ -201,6 +210,8 @@ }, { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "description": "Tests panel inheriting datasource from target when panel datasource was default", @@ -208,6 +219,8 @@ "targets": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "A" @@ -290,6 +303,8 @@ "panels": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "description": "Nested panel in collapsed row with default datasource", @@ -297,6 +312,8 @@ "targets": [ { "datasource": { + "apiVersion": "v2", + "type": "elasticsearch", "uid": "existing-target-uid" }, "refId": "A" diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v37.legend_normalization.v37.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v37.legend_normalization.v37.json index 84b41cc30b9..47b5329ee26 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v37.legend_normalization.v37.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v37.legend_normalization.v37.json @@ -117,6 +117,7 @@ "type": "timeseries" }, { + "autoMigrateFrom": "graph", "id": 12, "options": { "legend": { @@ -125,7 +126,7 @@ } }, "title": "Nested Panel with Hidden DisplayMode", - "type": "graph" + "type": "timeseries" }, { "id": 13, diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v42.harky_must.v42.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v42.harky_must.v42.json new file mode 100644 index 00000000000..7bf27227b56 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v42.harky_must.v42.json @@ -0,0 +1,62 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "id": 1, + "title": "Panel with hideFrom.viz = true", + "type": "timeseries" + } + ], + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "v42 Migration Test - Harky Must", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v42.hidefrom_tooltip.v42.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v42.hidefrom_tooltip.v42.json index 8ef37e2b890..1da5978b1a7 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v42.hidefrom_tooltip.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v42.hidefrom_tooltip.v42.json @@ -67,14 +67,23 @@ ] }, { + "__systemRef": "hideSeriesFrom", "matcher": { - "id": "__systemRef", - "options": "hiddenSeries" + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "foo" + ], + "prefix": "All except:", + "readOnly": true + } }, "properties": [ { "id": "custom.hideFrom", "value": { + "legend": false, "tooltip": true, "viz": true } @@ -121,6 +130,10 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byName", + "options": "Time" + }, "properties": [ { "id": "unit", @@ -143,13 +156,18 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, "properties": [ { - "id": "custom.hideFrom", - "value": { - "tooltip": false, - "viz": false - } + "id": "unit", + "value": "short" } ] } @@ -164,10 +182,15 @@ "defaults": {}, "overrides": [ { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, "properties": [ { "id": "custom.hideFrom", "value": { + "legend": false, "tooltip": true, "viz": true } diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json index 8cdf9a36a40..509bdb7a181 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json @@ -14,14 +14,20 @@ "type": "dashboard" }, { - "datasource": "prometheus", + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, "enable": true, "iconColor": "red", "name": "deployment", "query": "ALERTS{alertname=\"DeploymentStarted\"}" }, { - "datasource": "loki", + "datasource": { + "type": "loki", + "uid": "loki-uid" + }, "enable": false, "iconColor": "yellow", "name": "alerts", @@ -79,6 +85,7 @@ { "name": "environment", "options": [], + "query": "label_values(up, instance)", "type": "query" }, { @@ -86,6 +93,7 @@ "datasource": "prometheus", "name": "service", "options": [], + "query": "label_values(up, instance)", "type": "query" }, { diff --git a/apps/dashboard/pkg/migration/testutil/mocks.go b/apps/dashboard/pkg/migration/testutil/mocks.go index 1c70c397029..dbc7df72728 100644 --- a/apps/dashboard/pkg/migration/testutil/mocks.go +++ b/apps/dashboard/pkg/migration/testutil/mocks.go @@ -88,6 +88,30 @@ func (p *ConfigurableDataSourceProvider) getStandardTestDataSources() []schemave Name: "-- Mixed --", ID: 6, }, + { + Default: false, + UID: "influx-uid", + Type: "influxdb", + APIVersion: "v1", + Name: "InfluxDB Test Datasource", + ID: 7, + }, + { + Default: false, + UID: "cloudwatch-uid", + Type: "cloudwatch", + APIVersion: "v1", + Name: "CloudWatch Test Datasource", + ID: 8, + }, + { + Default: false, + UID: "-- Grafana --", + Type: "grafana", + APIVersion: "v1", + Name: "-- Grafana --", + ID: 9, + }, } } diff --git a/apps/dashboard/pkg/migration/testutil/mocks_test.go b/apps/dashboard/pkg/migration/testutil/mocks_test.go index 5d27074f79e..dfbd0e6b86e 100644 --- a/apps/dashboard/pkg/migration/testutil/mocks_test.go +++ b/apps/dashboard/pkg/migration/testutil/mocks_test.go @@ -15,7 +15,7 @@ func TestConfigurableDataSourceProvider(t *testing.T) { provider := NewDataSourceProvider(StandardTestConfig) dataSources := provider.GetDataSourceInfo(ctx) - require.Len(t, dataSources, 6) + require.Len(t, dataSources, 9) // Verify standard test configuration defaultDS := dataSources[0] @@ -58,8 +58,8 @@ func TestConfigurableDataSourceProvider(t *testing.T) { devDS1 := devProvider1.GetDataSourceInfo(ctx) devDS2 := devProvider2.GetDataSourceInfo(ctx) - require.Len(t, standardDS1, 6) - require.Len(t, standardDS2, 6) + require.Len(t, standardDS1, 9) + require.Len(t, standardDS2, 9) require.Len(t, devDS1, 6) require.Len(t, devDS2, 6) @@ -74,7 +74,7 @@ func TestConfigurableDataSourceProvider(t *testing.T) { provider := NewDataSourceProvider("unknown-config") dataSources := provider.GetDataSourceInfo(ctx) - require.Len(t, dataSources, 6) + require.Len(t, dataSources, 9) // Should default to standard configuration defaultDS := dataSources[0] diff --git a/apps/iam/go.mod b/apps/iam/go.mod index b98eea39b77..bc185ae88f3 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -100,24 +100,24 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/at-wat/mqtt-go v0.19.4 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect - github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect - github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.10 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect github.com/aws/smithy-go v1.23.1 // indirect github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect @@ -151,7 +151,6 @@ require ( github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/dgraph-io/badger/v4 v4.7.0 // indirect @@ -182,7 +181,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.2 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-ldap/ldap/v3 v3.4.4 // indirect - github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.0 // indirect @@ -235,7 +234,7 @@ require ( github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-aws-sdk v1.2.0 // indirect + github.com/grafana/grafana-aws-sdk v1.3.0 // indirect github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect github.com/grafana/grafana-plugin-sdk-go v0.281.0 // indirect github.com/grafana/grafana/apps/dashboard v0.0.0 // indirect @@ -295,7 +294,6 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lestrrat-go/strftime v1.0.4 // 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/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect @@ -361,7 +359,6 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect @@ -382,13 +379,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tetratelabs/wazero v1.8.2 // indirect github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect - github.com/tjhop/slog-gokit v0.1.3 // indirect + github.com/tjhop/slog-gokit v0.1.5 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // 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.17 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -455,7 +448,6 @@ require ( google.golang.org/protobuf v1.36.10 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // 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/mail.v2 v2.3.1 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index c23b88ab4f1..31148a98766 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -237,22 +237,22 @@ github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= -github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE= +github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= -github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0= -github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= +github.com/aws/aws-sdk-go-v2/config v1.31.10 h1:7LllDZAegXU3yk41mwM6KcPu0wmjKGQB1bg99bNdQm4= +github.com/aws/aws-sdk-go-v2/config v1.31.10/go.mod h1:Ge6gzXPjqu4v0oHvgAwvGzYcK921GU0hQM25WF/Kl+8= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 h1:gLD09eaJUdiszm7vd1btiQUYE0Hj+0I2b8AS+75z9AY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8/go.mod h1:4RW3oMPt1POR74qVOC4SbubxAwdP4pCT0nSw3jycOU4= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs= @@ -263,12 +263,12 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFie github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0/go.mod h1:UseIHRfrm7PqeZo6fcTb6FUCXzCnh1KJbQbmOfxArGM= github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJdQgRP5mKCTDt4GNk= github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U= github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 h1:zJeUxFP7+XP52u23vrp4zMcVhShTWbNO8dHV6xCSvFo= @@ -279,12 +279,12 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 h1:Pwbxovp github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6/go.mod h1:Z4xLt5mXspLKjBV92i165wAJ/3T6TIv4n7RtIS8pWV0= github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg= github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 h1:FTdEN9dtWPB0EOURNtDPmwGp6GGvMqRJCAihkSl/1No= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.4/go.mod h1:mYubxV9Ff42fZH4kexj43gFPhgc/LyC7KqvUKt1watc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 h1:I7ghctfGXrscr7r1Ga/mDqSJKm7Fkpl5Mwq79Z+rZqU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0/go.mod h1:Zo9id81XP6jbayIFWNuDpA6lMBWhsVy+3ou2jLa4JnA= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0= github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s= @@ -444,8 +444,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -595,8 +595,8 @@ github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXg github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= +github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -826,9 +826,6 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -858,8 +855,8 @@ github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+ github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c= github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= -github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw= -github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A= +github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= +github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc= github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI= @@ -882,8 +879,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLm github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78= github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw= github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y= -github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8= -github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= +github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg= +github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= @@ -1067,9 +1064,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1124,8 +1118,6 @@ github.com/m3db/prometheus_remote_client_golang v0.4.4 h1:DsAIjVKoCp7Ym35tAOFL1O github.com/m3db/prometheus_remote_client_golang v0.4.4/go.mod h1:wHfVbA3eAK6dQvKjCkHhusWYegCk3bDGkA15zymSHdc= github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4= github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0= -github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= @@ -1426,8 +1418,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -1458,7 +1450,6 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg= github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1467,11 +1458,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= @@ -1526,7 +1512,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= @@ -1541,8 +1526,8 @@ github.com/thejerf/slogassert v0.3.4/go.mod h1:0zn9ISLVKo1aPMTqcGfG1o6dWwt+Rk574 github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE9IrvgBA2C1mRN1lc= github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= -github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= +github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc= +github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= @@ -1559,16 +1544,7 @@ github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= -github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= -github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= -github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= -github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= -github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -1916,7 +1892,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2293,8 +2268,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/apps/iam/kinds/externalgroupmapping.cue b/apps/iam/kinds/externalgroupmapping.cue new file mode 100644 index 00000000000..b4a8bf2d7f5 --- /dev/null +++ b/apps/iam/kinds/externalgroupmapping.cue @@ -0,0 +1,20 @@ +package kinds + +import ( + "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" +) + +externalGroupMappingKind: { + kind: "ExternalGroupMapping" + pluralName: "ExternalGroupMappings" + codegen: { + ts: {enabled: false} + go: {enabled: true} + } +} + +externalGroupMappingv0alpha1: externalGroupMappingKind & { + schema: { + spec: v0alpha1.ExternalGroupMappingSpec + } +} diff --git a/apps/iam/kinds/manifest.cue b/apps/iam/kinds/manifest.cue index c9390acbf3e..bc9c3d47ad7 100644 --- a/apps/iam/kinds/manifest.cue +++ b/apps/iam/kinds/manifest.cue @@ -20,5 +20,6 @@ v0alpha1: { teamv0alpha1, teambindingv0alpha1, serviceaccountv0alpha1, + externalGroupMappingv0alpha1 ] } diff --git a/apps/iam/kinds/v0alpha1/externalgroupmappingspec.cue b/apps/iam/kinds/v0alpha1/externalgroupmappingspec.cue new file mode 100644 index 00000000000..175da727409 --- /dev/null +++ b/apps/iam/kinds/v0alpha1/externalgroupmappingspec.cue @@ -0,0 +1,6 @@ +package v0alpha1 + +ExternalGroupMappingSpec: { + teamRef: TeamRef + externalGroupId: string +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_client_gen.go new file mode 100644 index 00000000000..8419fce6046 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_client_gen.go @@ -0,0 +1,80 @@ +package v0alpha1 + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/resource" +) + +type ExternalGroupMappingClient struct { + client *resource.TypedClient[*ExternalGroupMapping, *ExternalGroupMappingList] +} + +func NewExternalGroupMappingClient(client resource.Client) *ExternalGroupMappingClient { + return &ExternalGroupMappingClient{ + client: resource.NewTypedClient[*ExternalGroupMapping, *ExternalGroupMappingList](client, ExternalGroupMappingKind()), + } +} + +func NewExternalGroupMappingClientFromGenerator(generator resource.ClientGenerator) (*ExternalGroupMappingClient, error) { + c, err := generator.ClientFor(ExternalGroupMappingKind()) + if err != nil { + return nil, err + } + return NewExternalGroupMappingClient(c), nil +} + +func (c *ExternalGroupMappingClient) Get(ctx context.Context, identifier resource.Identifier) (*ExternalGroupMapping, error) { + return c.client.Get(ctx, identifier) +} + +func (c *ExternalGroupMappingClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*ExternalGroupMappingList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *ExternalGroupMappingClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*ExternalGroupMappingList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *ExternalGroupMappingClient) Create(ctx context.Context, obj *ExternalGroupMapping, opts resource.CreateOptions) (*ExternalGroupMapping, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = ExternalGroupMappingKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *ExternalGroupMappingClient) Update(ctx context.Context, obj *ExternalGroupMapping, opts resource.UpdateOptions) (*ExternalGroupMapping, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *ExternalGroupMappingClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*ExternalGroupMapping, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *ExternalGroupMappingClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_codec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_codec_gen.go new file mode 100644 index 00000000000..9e519d40b6f --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// ExternalGroupMappingJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type ExternalGroupMappingJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*ExternalGroupMappingJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*ExternalGroupMappingJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &ExternalGroupMappingJSONCodec{} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_metadata_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_metadata_gen.go new file mode 100644 index 00000000000..5b33b2d2279 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type ExternalGroupMappingMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewExternalGroupMappingMetadata creates a new ExternalGroupMappingMetadata object. +func NewExternalGroupMappingMetadata() *ExternalGroupMappingMetadata { + return &ExternalGroupMappingMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_object_gen.go new file mode 100644 index 00000000000..db20616c355 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_object_gen.go @@ -0,0 +1,293 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type ExternalGroupMapping struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the ExternalGroupMapping + Spec ExternalGroupMappingSpec `json:"spec" yaml:"spec"` +} + +func (o *ExternalGroupMapping) GetSpec() any { + return o.Spec +} + +func (o *ExternalGroupMapping) SetSpec(spec any) error { + cast, ok := spec.(ExternalGroupMappingSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *ExternalGroupMapping) GetSubresources() map[string]any { + return map[string]any{} +} + +func (o *ExternalGroupMapping) GetSubresource(name string) (any, bool) { + switch name { + default: + return nil, false + } +} + +func (o *ExternalGroupMapping) SetSubresource(name string, value any) error { + switch name { + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *ExternalGroupMapping) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *ExternalGroupMapping) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *ExternalGroupMapping) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *ExternalGroupMapping) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *ExternalGroupMapping) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *ExternalGroupMapping) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *ExternalGroupMapping) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *ExternalGroupMapping) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *ExternalGroupMapping) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *ExternalGroupMapping) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *ExternalGroupMapping) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *ExternalGroupMapping) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *ExternalGroupMapping) DeepCopy() *ExternalGroupMapping { + cpy := &ExternalGroupMapping{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *ExternalGroupMapping) DeepCopyInto(dst *ExternalGroupMapping) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) +} + +// Interface compliance compile-time check +var _ resource.Object = &ExternalGroupMapping{} + +// +k8s:openapi-gen=true +type ExternalGroupMappingList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []ExternalGroupMapping `json:"items" yaml:"items"` +} + +func (o *ExternalGroupMappingList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *ExternalGroupMappingList) Copy() resource.ListObject { + cpy := &ExternalGroupMappingList{ + TypeMeta: o.TypeMeta, + Items: make([]ExternalGroupMapping, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*ExternalGroupMapping); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *ExternalGroupMappingList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *ExternalGroupMappingList) SetItems(items []resource.Object) { + o.Items = make([]ExternalGroupMapping, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*ExternalGroupMapping) + } +} + +func (o *ExternalGroupMappingList) DeepCopy() *ExternalGroupMappingList { + cpy := &ExternalGroupMappingList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *ExternalGroupMappingList) DeepCopyInto(dst *ExternalGroupMappingList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &ExternalGroupMappingList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *ExternalGroupMappingSpec) DeepCopy() *ExternalGroupMappingSpec { + cpy := &ExternalGroupMappingSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *ExternalGroupMappingSpec) DeepCopyInto(dst *ExternalGroupMappingSpec) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_schema_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_schema_gen.go new file mode 100644 index 00000000000..91090a9b460 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaExternalGroupMapping = resource.NewSimpleSchema("iam.grafana.app", "v0alpha1", &ExternalGroupMapping{}, &ExternalGroupMappingList{}, resource.WithKind("ExternalGroupMapping"), + resource.WithPlural("externalgroupmappings"), resource.WithScope(resource.NamespacedScope)) + kindExternalGroupMapping = resource.Kind{ + Schema: schemaExternalGroupMapping, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &ExternalGroupMappingJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func ExternalGroupMappingKind() resource.Kind { + return kindExternalGroupMapping +} + +// Schema returns a resource.SimpleSchema representation of ExternalGroupMapping +func ExternalGroupMappingSchema() *resource.SimpleSchema { + return schemaExternalGroupMapping +} + +// Interface compliance checks +var _ resource.Schema = kindExternalGroupMapping diff --git a/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_spec_gen.go new file mode 100644 index 00000000000..8bfbfe5b04a --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/externalgroupmapping_spec_gen.go @@ -0,0 +1,27 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type ExternalGroupMappingTeamRef struct { + // Name is the unique identifier for a team. + Name string `json:"name"` +} + +// NewExternalGroupMappingTeamRef creates a new ExternalGroupMappingTeamRef object. +func NewExternalGroupMappingTeamRef() *ExternalGroupMappingTeamRef { + return &ExternalGroupMappingTeamRef{} +} + +// +k8s:openapi-gen=true +type ExternalGroupMappingSpec struct { + TeamRef ExternalGroupMappingTeamRef `json:"teamRef"` + ExternalGroupId string `json:"externalGroupId"` +} + +// NewExternalGroupMappingSpec creates a new ExternalGroupMappingSpec object. +func NewExternalGroupMappingSpec() *ExternalGroupMappingSpec { + return &ExternalGroupMappingSpec{ + TeamRef: *NewExternalGroupMappingTeamRef(), + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/register.go b/apps/iam/pkg/apis/iam/v0alpha1/register.go index 16ee89847ef..54d60c7bd6f 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/register.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/register.go @@ -206,6 +206,30 @@ var TeamBindingResourceInfo = utils.NewResourceInfo( }, ) +var ExternalGroupMappingResourceInfo = utils.NewResourceInfo(GROUP, VERSION, + "externalgroupmappings", "externalgroupmapping", "ExternalGroupMapping", + func() runtime.Object { return &ExternalGroupMapping{} }, + func() runtime.Object { return &ExternalGroupMappingList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + mapping, ok := obj.(*ExternalGroupMapping) + if ok { + if mapping != nil { + return []interface{}{ + mapping.Name, + mapping.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + } + return nil, fmt.Errorf("expected external group mapping") + }, + }, +) + var RoleBindingInfo = utils.NewResourceInfo(GROUP, VERSION, "rolebindings", "rolebinding", "RoleBinding", func() runtime.Object { return &RoleBinding{} }, @@ -295,6 +319,8 @@ func AddAuthNKnownTypes(scheme *runtime.Scheme) error { &TeamList{}, &TeamBinding{}, &TeamBindingList{}, + &ExternalGroupMapping{}, + &ExternalGroupMappingList{}, // For now these are registered in pkg/apis/iam/v0alpha1/register.go // &UserTeamList{}, // &ServiceAccountTokenList{}, diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index ac03b342c18..f0c517afd38 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -18,6 +18,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus": schema_pkg_apis_iam_v0alpha1_CoreRoleStatus(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolespecPermission": schema_pkg_apis_iam_v0alpha1_CoreRolespecPermission(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_CoreRolestatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping": schema_pkg_apis_iam_v0alpha1_ExternalGroupMapping(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": schema_pkg_apis_iam_v0alpha1_GlobalRole(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBinding": schema_pkg_apis_iam_v0alpha1_GlobalRoleBinding(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingList": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingList(ref), @@ -348,6 +352,145 @@ func schema_pkg_apis_iam_v0alpha1_CoreRolestatusOperatorState(ref common.Referen } } +func schema_pkg_apis_iam_v0alpha1_ExternalGroupMapping(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.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec is the spec of the ExternalGroupMapping", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingList(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/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "teamRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef"), + }, + }, + "externalGroupId": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"teamRef", "externalGroupId"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the unique identifier for a team.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + func schema_pkg_apis_iam_v0alpha1_GlobalRole(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index 644ad2ea2d1..388c0dca609 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -96,6 +96,13 @@ var appManifestData = app.ManifestData{ Scope: "Namespaced", Conversion: false, }, + + { + Kind: "ExternalGroupMapping", + Plural: "ExternalGroupMappings", + Scope: "Namespaced", + Conversion: false, + }, }, Routes: app.ManifestVersionRoutes{ Namespaced: map[string]spec3.PathProps{}, @@ -115,16 +122,17 @@ func RemoteManifest() app.Manifest { } var kindVersionToGoType = map[string]resource.Kind{ - "GlobalRole/v0alpha1": v0alpha1.GlobalRoleKind(), - "GlobalRoleBinding/v0alpha1": v0alpha1.GlobalRoleBindingKind(), - "CoreRole/v0alpha1": v0alpha1.CoreRoleKind(), - "Role/v0alpha1": v0alpha1.RoleKind(), - "RoleBinding/v0alpha1": v0alpha1.RoleBindingKind(), - "ResourcePermission/v0alpha1": v0alpha1.ResourcePermissionKind(), - "User/v0alpha1": v0alpha1.UserKind(), - "Team/v0alpha1": v0alpha1.TeamKind(), - "TeamBinding/v0alpha1": v0alpha1.TeamBindingKind(), - "ServiceAccount/v0alpha1": v0alpha1.ServiceAccountKind(), + "GlobalRole/v0alpha1": v0alpha1.GlobalRoleKind(), + "GlobalRoleBinding/v0alpha1": v0alpha1.GlobalRoleBindingKind(), + "CoreRole/v0alpha1": v0alpha1.CoreRoleKind(), + "Role/v0alpha1": v0alpha1.RoleKind(), + "RoleBinding/v0alpha1": v0alpha1.RoleBindingKind(), + "ResourcePermission/v0alpha1": v0alpha1.ResourcePermissionKind(), + "User/v0alpha1": v0alpha1.UserKind(), + "Team/v0alpha1": v0alpha1.TeamKind(), + "TeamBinding/v0alpha1": v0alpha1.TeamBindingKind(), + "ServiceAccount/v0alpha1": v0alpha1.ServiceAccountKind(), + "ExternalGroupMapping/v0alpha1": v0alpha1.ExternalGroupMappingKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. diff --git a/apps/iam/pkg/reconcilers/zanzana_service.go b/apps/iam/pkg/reconcilers/zanzana_service.go index 10897a446c7..fd666023228 100644 --- a/apps/iam/pkg/reconcilers/zanzana_service.go +++ b/apps/iam/pkg/reconcilers/zanzana_service.go @@ -5,12 +5,13 @@ import ( "fmt" "strings" - authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" - "github.com/grafana/grafana/pkg/services/authz/zanzana" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + + authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" + "github.com/grafana/grafana/pkg/services/authz/zanzana" ) type ZanzanaPermissionStore struct { @@ -34,44 +35,22 @@ func (c *ZanzanaPermissionStore) SetFolderParent(ctx context.Context, namespace, ) defer span.End() - err := c.DeleteFolderParents(ctx, namespace, folderUID) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to delete existing folder parents") - return err - } - - if parentUID == "" { - // Setting the parent to empty means the folder is at root which Zanzana doesn't care about. - return nil - } - - user, err := toFolderTuple(parentUID) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to create parent tuple") - return err - } - - object, err := toFolderTuple(folderUID) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to create folder tuple") - return err - } - - if err := c.zanzanaClient.Write(ctx, &authzextv1.WriteRequest{ + if err := c.zanzanaClient.Mutate(ctx, &authzextv1.MutateRequest{ Namespace: namespace, - Writes: &authzextv1.WriteRequestWrites{ - TupleKeys: []*authzextv1.TupleKey{{ - User: user, - Relation: zanzana.RelationParent, - Object: object, - }}, + Operations: []*authzextv1.MutateOperation{ + { + Operation: &authzextv1.MutateOperation_SetFolderParent{ + SetFolderParent: &authzextv1.SetFolderParentOperation{ + Folder: folderUID, + Parent: parentUID, + DeleteExisting: true, + }, + }, + }, }, }); err != nil { span.RecordError(err) - span.SetStatus(codes.Error, "failed to write parent tuple to zanzana") + span.SetStatus(codes.Error, "failed to update folder parent in zanzana") return err } @@ -133,24 +112,23 @@ func (c *ZanzanaPermissionStore) DeleteFolderParents(ctx context.Context, namesp ) defer span.End() - tuples, err := c.listFolderParentRelations(ctx, namespace, folderUID) - if err != nil { + if err := c.zanzanaClient.Mutate(ctx, &authzextv1.MutateRequest{ + Namespace: namespace, + Operations: []*authzextv1.MutateOperation{ + { + Operation: &authzextv1.MutateOperation_DeleteFolder{ + DeleteFolder: &authzextv1.DeleteFolderOperation{ + Folder: folderUID, + DeleteExisting: true, + }, + }, + }, + }, + }); err != nil { span.RecordError(err) - span.SetStatus(codes.Error, "failed to list folder parent relations") + span.SetStatus(codes.Error, "failed to delete folder parents in zanzana") return err } - - span.SetAttributes(attribute.Int("tuples.toDelete.count", len(tuples))) - - if len(tuples) > 0 { - err = c.deleteTuples(ctx, namespace, tuples) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to delete tuples") - return err - } - } - return nil } @@ -212,41 +190,6 @@ func (c *ZanzanaPermissionStore) listFolderParentRelations(ctx context.Context, return list.Tuples, nil } -func (c *ZanzanaPermissionStore) deleteTuples(ctx context.Context, namespace string, tuples []*authzextv1.Tuple) error { - tracer := otel.GetTracerProvider().Tracer("zanzana-folder-reconciler") - ctx, span := tracer.Start(ctx, "zanzana-permission-store.delete-tuples", - trace.WithAttributes( - attribute.String("namespace", namespace), - attribute.Int("tuples.count", len(tuples)), - ), - ) - defer span.End() - - tupleKeys := make([]*authzextv1.TupleKeyWithoutCondition, 0, len(tuples)) - for _, t := range tuples { - tupleKeys = append(tupleKeys, &authzextv1.TupleKeyWithoutCondition{ - User: t.Key.User, - Relation: t.Key.Relation, - Object: t.Key.Object, - }) - } - - err := c.zanzanaClient.Write(ctx, &authzextv1.WriteRequest{ - Namespace: namespace, - Deletes: &authzextv1.WriteRequestDeletes{ - TupleKeys: tupleKeys, - }, - }) - - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to delete tuples in zanzana") - return err - } - - return nil -} - func toFolderTuple(UID string) (string, error) { if strings.ContainsAny(UID, "#:") { return "", fmt.Errorf("UID contains invalid characters: %s", UID) diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index f0ef53718bc..e4a419d9214 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -6,6 +6,7 @@ require ( github.com/grafana/grafana-app-sdk v0.48.1 github.com/grafana/grafana-app-sdk/logging v0.48.1 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde + github.com/stretchr/testify v1.11.1 k8s.io/apimachinery v0.34.1 k8s.io/apiserver v0.34.1 k8s.io/klog/v2 v2.130.1 diff --git a/apps/plugins/pkg/app/install/registrar.go b/apps/plugins/pkg/app/install/registrar.go index 98d7c02b94b..fe747eb2806 100644 --- a/apps/plugins/pkg/app/install/registrar.go +++ b/apps/plugins/pkg/app/install/registrar.go @@ -93,6 +93,7 @@ func equalStringPointers(a, b *string) bool { type InstallRegistrar struct { clientGenerator resource.ClientGenerator client *pluginsv0alpha1.PluginClient + clientErr error clientOnce sync.Once } @@ -107,20 +108,21 @@ func (r *InstallRegistrar) GetClient() (*pluginsv0alpha1.PluginClient, error) { r.clientOnce.Do(func() { client, err := pluginsv0alpha1.NewPluginClientFromGenerator(r.clientGenerator) if err != nil { + r.clientErr = err r.client = nil return } r.client = client }) - return r.client, nil + return r.client, r.clientErr } // Register creates or updates a plugin install in the registry. func (r *InstallRegistrar) Register(ctx context.Context, namespace string, install *PluginInstall) error { client, err := r.GetClient() if err != nil { - return nil + return err } identifier := resource.Identifier{ Namespace: namespace, @@ -132,9 +134,12 @@ func (r *InstallRegistrar) Register(ctx context.Context, namespace string, insta return err } - if existing != nil && install.ShouldUpdate(existing) { - _, err = client.Update(ctx, install.ToPluginInstallV0Alpha1(namespace), resource.UpdateOptions{ResourceVersion: existing.ResourceVersion}) - return err + if existing != nil { + if install.ShouldUpdate(existing) { + _, err = client.Update(ctx, install.ToPluginInstallV0Alpha1(namespace), resource.UpdateOptions{ResourceVersion: existing.ResourceVersion}) + return err + } + return nil } _, err = client.Create(ctx, install.ToPluginInstallV0Alpha1(namespace), resource.CreateOptions{}) @@ -155,6 +160,10 @@ func (r *InstallRegistrar) Unregister(ctx context.Context, namespace string, nam if err != nil && !errorsK8s.IsNotFound(err) { return err } + // if the plugin doesn't exist, nothing to unregister + if existing == nil { + return nil + } // if the source is different, do not unregister if existingSource, ok := existing.Annotations[PluginInstallSourceAnnotation]; ok && existingSource != source { return nil diff --git a/apps/plugins/pkg/app/install/registrar_test.go b/apps/plugins/pkg/app/install/registrar_test.go new file mode 100644 index 00000000000..36359896ae6 --- /dev/null +++ b/apps/plugins/pkg/app/install/registrar_test.go @@ -0,0 +1,908 @@ +package install + +import ( + "context" + "errors" + "testing" + + "github.com/grafana/grafana-app-sdk/resource" + "github.com/stretchr/testify/require" + errorsK8s "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +func TestPluginInstall_ShouldUpdate(t *testing.T) { + baseExisting := &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + Spec: pluginsv0alpha1.PluginSpec{ + Id: "plugin-1", + Version: "1.0.0", + Class: pluginsv0alpha1.PluginSpecClass(ClassExternal), + }, + } + + baseInstall := PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + } + + tests := []struct { + name string + modifyInstall func(*PluginInstall) + modifyExisting func(*pluginsv0alpha1.Plugin) + expectUpdate bool + }{ + { + name: "no changes", + expectUpdate: false, + }, + { + name: "version differs", + modifyInstall: func(pi *PluginInstall) { + pi.Version = "2.0.0" + }, + expectUpdate: true, + }, + { + name: "class differs", + modifyInstall: func(pi *PluginInstall) { + pi.Class = ClassCore + }, + expectUpdate: true, + }, + { + name: "url differs", + modifyInstall: func(pi *PluginInstall) { + pi.URL = "https://example.com/plugin.zip" + }, + expectUpdate: true, + }, + { + name: "source differs", + modifyExisting: func(existing *pluginsv0alpha1.Plugin) { + existing.Annotations[PluginInstallSourceAnnotation] = SourceUnknown + }, + expectUpdate: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + existing := baseExisting.DeepCopy() + install := baseInstall + + if tt.modifyExisting != nil { + tt.modifyExisting(existing) + } + if tt.modifyInstall != nil { + tt.modifyInstall(&install) + } + + require.Equal(t, tt.expectUpdate, install.ShouldUpdate(existing)) + }) + } +} + +func TestInstallRegistrar_Register(t *testing.T) { + tests := []struct { + name string + install *PluginInstall + existing *pluginsv0alpha1.Plugin + existingErr error + expectedCreates int + expectedUpdates int + expectError bool + }{ + { + name: "creates plugin when not found", + install: &PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + existingErr: errorsK8s.NewNotFound(pluginGroupResource(), "plugin-1"), + expectedCreates: 1, + }, + { + name: "updates plugin when fields change", + install: &PluginInstall{ + ID: "plugin-1", + Version: "2.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + existing: &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + ResourceVersion: "7", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + Spec: pluginsv0alpha1.PluginSpec{ + Id: "plugin-1", + Version: "1.0.0", + Class: pluginsv0alpha1.PluginSpecClass(ClassExternal), + }, + }, + expectedUpdates: 1, + }, + { + name: "skips create when plugin matches", + install: &PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + existing: &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + ResourceVersion: "9", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + Spec: pluginsv0alpha1.PluginSpec{ + Id: "plugin-1", + Version: "1.0.0", + Class: pluginsv0alpha1.PluginSpecClass(ClassExternal), + }, + }, + }, + { + name: "returns error on unexpected get failure", + install: &PluginInstall{ + ID: "plugin-err", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + existingErr: errorsK8s.NewInternalError(errors.New("boom")), + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + createCalls := 0 + updateCalls := 0 + var receivedResourceVersions []string + var updatedPlugins []*pluginsv0alpha1.Plugin + + fakeClient := &fakePluginInstallClient{ + getFunc: func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + if tt.existingErr != nil { + return nil, tt.existingErr + } + if tt.existing == nil { + return nil, errorsK8s.NewNotFound(pluginGroupResource(), "plugin-1") + } + return tt.existing.DeepCopy(), nil + }, + createFunc: func(context.Context, *pluginsv0alpha1.Plugin, resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { + createCalls++ + return tt.install.ToPluginInstallV0Alpha1("org-1"), nil + }, + updateFunc: func(_ context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + updateCalls++ + receivedResourceVersions = append(receivedResourceVersions, opts.ResourceVersion) + updatedPlugins = append(updatedPlugins, obj) + return obj, nil + }, + } + + registrar := NewInstallRegistrar(&fakeClientGenerator{client: fakeClient}) + + err := registrar.Register(ctx, "org-1", tt.install) + if tt.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.expectedCreates, createCalls) + require.Equal(t, tt.expectedUpdates, updateCalls) + + if tt.expectedUpdates > 0 { + require.Equal(t, []string{tt.existing.ResourceVersion}, receivedResourceVersions) + require.Len(t, updatedPlugins, 1) + require.Equal(t, tt.install.Version, updatedPlugins[0].Spec.Version) + } + }) + } +} + +func pluginGroupResource() schema.GroupResource { + return schema.GroupResource{Group: pluginsv0alpha1.APIGroup, Resource: "plugininstalls"} +} + +type fakePluginInstallClient struct { + listAllFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) + getFunc func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) + createFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) + updateFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) + deleteFunc func(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error +} + +func (f *fakePluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + if f.getFunc != nil { + return f.getFunc(ctx, identifier) + } + return nil, errorsK8s.NewNotFound(pluginGroupResource(), identifier.Name) +} + +func (f *fakePluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + if f.listAllFunc != nil { + return f.listAllFunc(ctx, namespace, opts) + } + return &pluginsv0alpha1.PluginList{}, nil +} + +func (f *fakePluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + return f.ListAll(ctx, namespace, opts) +} + +func (f *fakePluginInstallClient) Create(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { + if f.createFunc != nil { + return f.createFunc(ctx, obj, opts) + } + return obj, nil +} + +func (f *fakePluginInstallClient) Update(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + if f.updateFunc != nil { + return f.updateFunc(ctx, obj, opts) + } + return obj, nil +} + +func (f *fakePluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus pluginsv0alpha1.PluginStatus, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, nil +} + +func (f *fakePluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, nil +} + +func (f *fakePluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + if f.deleteFunc != nil { + return f.deleteFunc(ctx, identifier, opts) + } + return nil +} + +type fakeClientGenerator struct { + client *fakePluginInstallClient + shouldError bool +} + +func (f *fakeClientGenerator) ClientFor(resource.Kind) (resource.Client, error) { + if f.shouldError { + return nil, errors.New("client generation failed") + } + return &fakeResourceClient{client: f.client}, nil +} + +type fakeResourceClient struct { + client *fakePluginInstallClient +} + +func (f *fakeResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) { + return f.client.Get(ctx, identifier) +} + +func (f *fakeResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error { + obj, err := f.client.Get(ctx, identifier) + if err != nil { + return err + } + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *obj + } + return nil +} + +func (f *fakeResourceClient) List(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { + return f.client.ListAll(ctx, namespace, options) +} + +func (f *fakeResourceClient) ListInto(ctx context.Context, namespace string, options resource.ListOptions, into resource.ListObject) error { + list, err := f.client.ListAll(ctx, namespace, options) + if err != nil { + return err + } + if target, ok := into.(*pluginsv0alpha1.PluginList); ok { + *target = *list + } + return nil +} + +func (f *fakeResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions) (resource.Object, error) { + plugin := obj.(*pluginsv0alpha1.Plugin) + return f.client.Create(ctx, plugin, options) +} + +func (f *fakeResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions, into resource.Object) error { + created, err := f.Create(ctx, identifier, obj, options) + if err != nil { + return err + } + if plugin, ok := created.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions) (resource.Object, error) { + plugin := obj.(*pluginsv0alpha1.Plugin) + return f.client.Update(ctx, plugin, options) +} + +func (f *fakeResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions, into resource.Object) error { + updated, err := f.Update(ctx, identifier, obj, options) + if err != nil { + return err + } + if plugin, ok := updated.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions) (resource.Object, error) { + return nil, nil +} + +func (f *fakeResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions, into resource.Object) error { + return nil +} + +func (f *fakeResourceClient) Delete(ctx context.Context, identifier resource.Identifier, options resource.DeleteOptions) error { + return f.client.Delete(ctx, identifier, options) +} + +func (f *fakeResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) { + return []byte{}, nil +} + +func (f *fakeResourceClient) Watch(ctx context.Context, namespace string, options resource.WatchOptions) (resource.WatchResponse, error) { + return &fakeWatchResponse{}, nil +} + +type fakeWatchResponse struct{} + +func (f *fakeWatchResponse) Stop() {} + +func (f *fakeWatchResponse) WatchEvents() <-chan resource.WatchEvent { + ch := make(chan resource.WatchEvent) + close(ch) + return ch +} + +func TestPluginInstall_ToPluginInstallV0Alpha1(t *testing.T) { + tests := []struct { + name string + install PluginInstall + namespace string + validate func(*testing.T, *pluginsv0alpha1.Plugin) + }{ + { + name: "empty URL creates nil pointer", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + namespace: "org-1", + validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) { + require.Nil(t, p.Spec.Url) + }, + }, + { + name: "non-empty URL creates pointer", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + URL: "https://example.com/plugin.zip", + Class: ClassExternal, + Source: SourcePluginStore, + }, + namespace: "org-1", + validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) { + require.NotNil(t, p.Spec.Url) + require.Equal(t, "https://example.com/plugin.zip", *p.Spec.Url) + }, + }, + { + name: "core class is mapped correctly", + install: PluginInstall{ + ID: "plugin-core", + Version: "2.0.0", + Class: ClassCore, + Source: SourcePluginStore, + }, + namespace: "org-2", + validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) { + require.Equal(t, pluginsv0alpha1.PluginSpecClass(ClassCore), p.Spec.Class) + }, + }, + { + name: "cdn class is mapped correctly", + install: PluginInstall{ + ID: "plugin-cdn", + Version: "3.0.0", + Class: ClassCDN, + Source: SourcePluginStore, + }, + namespace: "org-3", + validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) { + require.Equal(t, pluginsv0alpha1.PluginSpecClass(ClassCDN), p.Spec.Class) + }, + }, + { + name: "source annotation is set correctly", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourceUnknown, + }, + namespace: "org-1", + validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) { + require.Equal(t, SourceUnknown, p.Annotations[PluginInstallSourceAnnotation]) + }, + }, + { + name: "namespace and name are set correctly", + install: PluginInstall{ + ID: "my-plugin", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + namespace: "my-namespace", + validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) { + require.Equal(t, "my-namespace", p.Namespace) + require.Equal(t, "my-plugin", p.Name) + require.Equal(t, "my-plugin", p.Spec.Id) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.install.ToPluginInstallV0Alpha1(tt.namespace) + require.NotNil(t, result) + require.Equal(t, tt.namespace, result.Namespace) + require.Equal(t, tt.install.ID, result.Name) + require.Equal(t, tt.install.ID, result.Spec.Id) + require.Equal(t, tt.install.Version, result.Spec.Version) + tt.validate(t, result) + }) + } +} + +func TestEqualStringPointers(t *testing.T) { + str1 := "value1" + str2 := "value2" + str3 := "value1" + + tests := []struct { + name string + a *string + b *string + expected bool + }{ + { + name: "both nil", + a: nil, + b: nil, + expected: true, + }, + { + name: "first nil, second non-nil", + a: nil, + b: &str1, + expected: false, + }, + { + name: "first non-nil, second nil", + a: &str1, + b: nil, + expected: false, + }, + { + name: "both non-nil with same value", + a: &str1, + b: &str3, + expected: true, + }, + { + name: "both non-nil with different values", + a: &str1, + b: &str2, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := equalStringPointers(tt.a, tt.b) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestPluginInstall_ShouldUpdate_URLTransitions(t *testing.T) { + existingURL := "https://old.example.com/plugin.zip" + newURL := "https://new.example.com/plugin.zip" + + tests := []struct { + name string + install PluginInstall + existingURL *string + expectUpdate bool + }{ + { + name: "URL transition from nil to non-nil", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + URL: newURL, + Class: ClassExternal, + Source: SourcePluginStore, + }, + existingURL: nil, + expectUpdate: true, + }, + { + name: "URL transition from non-nil to nil", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + URL: "", + Class: ClassExternal, + Source: SourcePluginStore, + }, + existingURL: &existingURL, + expectUpdate: true, + }, + { + name: "URL stays nil", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + URL: "", + Class: ClassExternal, + Source: SourcePluginStore, + }, + existingURL: nil, + expectUpdate: false, + }, + { + name: "URL stays same non-nil value", + install: PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + URL: existingURL, + Class: ClassExternal, + Source: SourcePluginStore, + }, + existingURL: &existingURL, + expectUpdate: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + existing := &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + Spec: pluginsv0alpha1.PluginSpec{ + Id: "plugin-1", + Version: "1.0.0", + Url: tt.existingURL, + Class: pluginsv0alpha1.PluginSpecClass(ClassExternal), + }, + } + + require.Equal(t, tt.expectUpdate, tt.install.ShouldUpdate(existing)) + }) + } +} + +func TestInstallRegistrar_GetClient(t *testing.T) { + t.Run("successfully creates client on first call", func(t *testing.T) { + fakeClient := &fakePluginInstallClient{} + generator := &fakeClientGenerator{client: fakeClient} + registrar := NewInstallRegistrar(generator) + + client, err := registrar.GetClient() + require.NoError(t, err) + require.NotNil(t, client) + }) + + t.Run("returns same client on subsequent calls", func(t *testing.T) { + fakeClient := &fakePluginInstallClient{} + generator := &fakeClientGenerator{client: fakeClient} + registrar := NewInstallRegistrar(generator) + + client1, err1 := registrar.GetClient() + require.NoError(t, err1) + + client2, err2 := registrar.GetClient() + require.NoError(t, err2) + + require.Equal(t, client1, client2) + }) + + t.Run("returns error when client generation fails", func(t *testing.T) { + generator := &fakeClientGenerator{client: nil, shouldError: true} + registrar := NewInstallRegistrar(generator) + + client, err := registrar.GetClient() + require.Error(t, err) + require.Nil(t, client) + }) +} + +func TestInstallRegistrar_Register_ErrorCases(t *testing.T) { + tests := []struct { + name string + install *PluginInstall + setupClient func(*fakePluginInstallClient) + expectError bool + }{ + { + name: "create fails", + install: &PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + setupClient: func(fc *fakePluginInstallClient) { + fc.getFunc = func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + return nil, errorsK8s.NewNotFound(pluginGroupResource(), "plugin-1") + } + fc.createFunc = func(context.Context, *pluginsv0alpha1.Plugin, resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, errors.New("create failed") + } + }, + expectError: true, + }, + { + name: "update fails", + install: &PluginInstall{ + ID: "plugin-1", + Version: "2.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + }, + setupClient: func(fc *fakePluginInstallClient) { + fc.getFunc = func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + return &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + ResourceVersion: "5", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + Spec: pluginsv0alpha1.PluginSpec{ + Id: "plugin-1", + Version: "1.0.0", + Class: pluginsv0alpha1.PluginSpecClass(ClassExternal), + }, + }, nil + } + fc.updateFunc = func(context.Context, *pluginsv0alpha1.Plugin, resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, errors.New("update failed") + } + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + fakeClient := &fakePluginInstallClient{} + tt.setupClient(fakeClient) + + registrar := NewInstallRegistrar(&fakeClientGenerator{client: fakeClient}) + + err := registrar.Register(ctx, "org-1", tt.install) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestInstallRegistrar_Unregister(t *testing.T) { + tests := []struct { + name string + namespace string + pluginName string + source Source + existing *pluginsv0alpha1.Plugin + existingErr error + expectedCalls int + expectError bool + }{ + { + name: "successfully deletes plugin with matching source", + namespace: "org-1", + pluginName: "plugin-1", + source: SourcePluginStore, + existing: &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + }, + expectedCalls: 1, + }, + { + name: "plugin not found should not error", + namespace: "org-1", + pluginName: "plugin-nonexistent", + source: SourcePluginStore, + existingErr: errorsK8s.NewNotFound(pluginGroupResource(), "plugin-nonexistent"), + expectedCalls: 0, + expectError: false, + }, + { + name: "skips delete when source doesn't match", + namespace: "org-1", + pluginName: "plugin-1", + source: SourcePluginStore, + existing: &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourceUnknown, + }, + }, + }, + expectedCalls: 0, + }, + { + name: "returns error on unexpected get failure", + namespace: "org-1", + pluginName: "plugin-err", + source: SourcePluginStore, + existingErr: errorsK8s.NewInternalError(errors.New("get failed")), + expectedCalls: 0, + expectError: true, + }, + { + name: "delete failure returns error", + namespace: "org-1", + pluginName: "plugin-1", + source: SourcePluginStore, + existing: &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + Annotations: map[string]string{ + PluginInstallSourceAnnotation: SourcePluginStore, + }, + }, + }, + expectedCalls: 1, + expectError: true, + }, + { + name: "handles missing source annotation", + namespace: "org-1", + pluginName: "plugin-1", + source: SourcePluginStore, + existing: &pluginsv0alpha1.Plugin{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "plugin-1", + Annotations: map[string]string{}, + }, + }, + expectedCalls: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + deleteCalls := 0 + + fakeClient := &fakePluginInstallClient{ + getFunc: func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + if tt.existingErr != nil { + return nil, tt.existingErr + } + if tt.existing == nil { + return nil, errorsK8s.NewNotFound(pluginGroupResource(), tt.pluginName) + } + return tt.existing.DeepCopy(), nil + }, + deleteFunc: func(context.Context, resource.Identifier, resource.DeleteOptions) error { + deleteCalls++ + if tt.name == "delete failure returns error" { + return errors.New("delete failed") + } + return nil + }, + } + + registrar := NewInstallRegistrar(&fakeClientGenerator{client: fakeClient}) + + err := registrar.Unregister(ctx, tt.namespace, tt.pluginName, tt.source) + + require.Equal(t, tt.expectedCalls, deleteCalls) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestInstallRegistrar_GetClientError(t *testing.T) { + t.Run("Register returns error with nil client", func(t *testing.T) { + ctx := context.Background() + generator := &fakeClientGenerator{client: nil, shouldError: true} + registrar := NewInstallRegistrar(generator) + + install := &PluginInstall{ + ID: "plugin-1", + Version: "1.0.0", + Class: ClassExternal, + Source: SourcePluginStore, + } + + err := registrar.Register(ctx, "org-1", install) + require.Error(t, err) + }) + + t.Run("Unregister returns error with nil client", func(t *testing.T) { + ctx := context.Background() + generator := &fakeClientGenerator{client: nil, shouldError: true} + registrar := NewInstallRegistrar(generator) + + err := registrar.Unregister(ctx, "org-1", "plugin-1", SourcePluginStore) + require.Error(t, err) + }) +} diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index ad4bf7e3143..919602234e8 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -10,7 +10,7 @@ require ( github.com/grafana/grafana-app-sdk/logging v0.48.1 github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 - github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 + github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b github.com/migueleliasweb/go-github-mock v1.1.0 github.com/stretchr/testify v1.11.1 golang.org/x/oauth2 v0.32.0 diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index a51e063c607..88f7288f7ee 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -70,8 +70,8 @@ github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f h1:f+Z github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f/go.mod h1:RA8mP8KVIwKXBx3Ssqa/uEBABib5LvUWYPVMxrNvnP0= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= -github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8= -github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= +github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg= +github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/apps/provisioning/pkg/controller/historyjob.go b/apps/provisioning/pkg/controller/historyjob.go index 2e4e13f359d..bdfefb0b613 100644 --- a/apps/provisioning/pkg/controller/historyjob.go +++ b/apps/provisioning/pkg/controller/historyjob.go @@ -58,32 +58,42 @@ func NewHistoryJobController( func (c *HistoryJobController) cleanupJob(obj interface{}) { job, ok := obj.(*provisioning.HistoricJob) if !ok { - c.logger.Error("Expected HistoricJob but got", "type", obj) + c.logger.Error("unexpected object type - expected HistoricJob", "type", obj) return } age := time.Since(job.CreationTimestamp.Time) - if age > c.expirationTime { - namespace := job.Namespace - ctx, _, err := identity.WithProvisioningIdentity(context.Background(), namespace) - if err != nil { - c.logger.Error("Failed to set provisioning identity for cleanup", "error", err) + + // Only cleanup jobs older than expiration time + if age <= c.expirationTime { + return + } + + logger := c.logger.With( + "job", job.Name, + "namespace", job.Namespace, + "age", age, + ) + + logger.Debug("start cleanup expired historic job") + + namespace := job.Namespace + ctx, _, err := identity.WithProvisioningIdentity(context.Background(), namespace) + if err != nil { + logger.Error("failed to set provisioning identity", "error", err) + return + } + + ctx = request.WithNamespace(ctx, namespace) + err = c.client.HistoricJobs(job.Namespace).Delete(ctx, job.Name, metav1.DeleteOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + logger.Debug("historic job already deleted") return } - - ctx = request.WithNamespace(ctx, namespace) - err = c.client.HistoricJobs(job.Namespace).Delete(ctx, job.Name, metav1.DeleteOptions{}) - if err != nil && !apierrors.IsNotFound(err) { - c.logger.Error("Failed to delete expired HistoryJob", - "namespace", job.Namespace, - "name", job.Name, - "age", age, - "error", err) - } else { - c.logger.Info("Deleted expired HistoryJob", - "namespace", job.Namespace, - "name", job.Name, - "age", age) - } + logger.Error("failed to delete expired historic job", "error", err) + return } + + logger.Info("deleted expired historic job") } diff --git a/apps/provisioning/pkg/jobs/validator.go b/apps/provisioning/pkg/jobs/validator.go new file mode 100644 index 00000000000..77232490b66 --- /dev/null +++ b/apps/provisioning/pkg/jobs/validator.go @@ -0,0 +1,172 @@ +package jobs + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/repository/git" + "github.com/grafana/grafana/apps/provisioning/pkg/safepath" +) + +// ValidateJob performs validation on the Job specification and returns an error if validation fails +func ValidateJob(job *provisioning.Job) error { + list := field.ErrorList{} + + // Validate action is specified + if job.Spec.Action == "" { + list = append(list, field.Required(field.NewPath("spec", "action"), "action must be specified")) + return toError(job.Name, list) // Early return since we can't validate further without knowing the action + } + + // Validate repository is specified + if job.Spec.Repository == "" { + list = append(list, field.Required(field.NewPath("spec", "repository"), "repository must be specified")) + } + + // Validate action-specific options + switch job.Spec.Action { + case provisioning.JobActionPull: + if job.Spec.Pull == nil { + list = append(list, field.Required(field.NewPath("spec", "pull"), "pull options required for pull action")) + } + // Pull options are simple, just incremental bool - no further validation needed + + case provisioning.JobActionPush: + if job.Spec.Push == nil { + list = append(list, field.Required(field.NewPath("spec", "push"), "push options required for push action")) + } else { + list = append(list, validateExportJobOptions(job.Spec.Push)...) + } + + case provisioning.JobActionPullRequest: + if job.Spec.PullRequest == nil { + list = append(list, field.Required(field.NewPath("spec", "pr"), "pull request options required for pr action")) + } + // PullRequest options are mostly informational - no strict validation needed + + case provisioning.JobActionMigrate: + if job.Spec.Migrate == nil { + list = append(list, field.Required(field.NewPath("spec", "migrate"), "migrate options required for migrate action")) + } + // Migrate options are simple - no further validation needed + + case provisioning.JobActionDelete: + if job.Spec.Delete == nil { + list = append(list, field.Required(field.NewPath("spec", "delete"), "delete options required for delete action")) + } else { + list = append(list, validateDeleteJobOptions(job.Spec.Delete)...) + } + + case provisioning.JobActionMove: + if job.Spec.Move == nil { + list = append(list, field.Required(field.NewPath("spec", "move"), "move options required for move action")) + } else { + list = append(list, validateMoveJobOptions(job.Spec.Move)...) + } + default: + list = append(list, field.Invalid(field.NewPath("spec", "action"), job.Spec.Action, "invalid action")) + } + + return toError(job.Name, list) +} + +// toError converts a field.ErrorList to an error, returning nil if the list is empty +func toError(name string, list field.ErrorList) error { + if len(list) == 0 { + return nil + } + return apierrors.NewInvalid( + provisioning.JobResourceInfo.GroupVersionKind().GroupKind(), + name, list) +} + +// validateExportJobOptions validates export (push) job options +func validateExportJobOptions(opts *provisioning.ExportJobOptions) field.ErrorList { + list := field.ErrorList{} + + // Validate branch name if specified + if opts.Branch != "" { + if !git.IsValidGitBranchName(opts.Branch) { + list = append(list, field.Invalid(field.NewPath("spec", "push", "branch"), opts.Branch, "invalid git branch name")) + } + } + + // Validate path if specified + if opts.Path != "" { + if err := safepath.IsSafe(opts.Path); err != nil { + list = append(list, field.Invalid(field.NewPath("spec", "push", "path"), opts.Path, err.Error())) + } + } + + return list +} + +// validateDeleteJobOptions validates delete job options +func validateDeleteJobOptions(opts *provisioning.DeleteJobOptions) field.ErrorList { + list := field.ErrorList{} + + // At least one of paths or resources must be specified + if len(opts.Paths) == 0 && len(opts.Resources) == 0 { + list = append(list, field.Required(field.NewPath("spec", "delete"), "at least one path or resource must be specified")) + return list + } + + // Validate paths + for i, p := range opts.Paths { + if err := safepath.IsSafe(p); err != nil { + list = append(list, field.Invalid(field.NewPath("spec", "delete", "paths").Index(i), p, err.Error())) + } + } + + // Validate resources + for i, r := range opts.Resources { + if r.Name == "" { + list = append(list, field.Required(field.NewPath("spec", "delete", "resources").Index(i).Child("name"), "resource name is required")) + } + if r.Kind == "" { + list = append(list, field.Required(field.NewPath("spec", "delete", "resources").Index(i).Child("kind"), "resource kind is required")) + } + } + + return list +} + +// validateMoveJobOptions validates move job options +func validateMoveJobOptions(opts *provisioning.MoveJobOptions) field.ErrorList { + list := field.ErrorList{} + + // At least one of paths or resources must be specified + if len(opts.Paths) == 0 && len(opts.Resources) == 0 { + list = append(list, field.Required(field.NewPath("spec", "move"), "at least one path or resource must be specified")) + return list + } + + // Target path is required + if opts.TargetPath == "" { + list = append(list, field.Required(field.NewPath("spec", "move", "targetPath"), "target path is required")) + } else { + if err := safepath.IsSafe(opts.TargetPath); err != nil { + list = append(list, field.Invalid(field.NewPath("spec", "move", "targetPath"), opts.TargetPath, err.Error())) + } + } + + // Validate source paths + for i, p := range opts.Paths { + if err := safepath.IsSafe(p); err != nil { + list = append(list, field.Invalid(field.NewPath("spec", "move", "paths").Index(i), p, err.Error())) + } + } + + // Validate resources + for i, r := range opts.Resources { + if r.Name == "" { + list = append(list, field.Required(field.NewPath("spec", "move", "resources").Index(i).Child("name"), "resource name is required")) + } + if r.Kind == "" { + list = append(list, field.Required(field.NewPath("spec", "move", "resources").Index(i).Child("kind"), "resource kind is required")) + } + } + + return list +} diff --git a/apps/provisioning/pkg/jobs/validator_test.go b/apps/provisioning/pkg/jobs/validator_test.go new file mode 100644 index 00000000000..fdd29598ebc --- /dev/null +++ b/apps/provisioning/pkg/jobs/validator_test.go @@ -0,0 +1,593 @@ +package jobs + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +func TestValidateJob(t *testing.T) { + tests := []struct { + name string + job *provisioning.Job + wantErr bool + validateError func(t *testing.T, err error) + }{ + { + name: "valid pull job", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPull, + Repository: "test-repo", + Pull: &provisioning.SyncJobOptions{ + Incremental: true, + }, + }, + }, + wantErr: false, + }, + { + name: "missing action", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.action: Required value") + }, + }, + { + name: "invalid action", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobAction("invalid"), + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.action: Invalid value") + }, + }, + { + name: "missing repository", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPull, + Pull: &provisioning.SyncJobOptions{ + Incremental: true, + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.repository: Required value") + }, + }, + { + name: "pull action without pull options", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPull, + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.pull: Required value") + }, + }, + { + name: "push action without push options", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPush, + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.push: Required value") + }, + }, + { + name: "valid push job with valid branch", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPush, + Repository: "test-repo", + Push: &provisioning.ExportJobOptions{ + Branch: "main", + Message: "Test commit", + }, + }, + }, + wantErr: false, + }, + { + name: "push job with invalid branch name", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPush, + Repository: "test-repo", + Push: &provisioning.ExportJobOptions{ + Branch: "feature..branch", // Invalid: contains consecutive dots + Message: "Test commit", + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.push.branch") + require.Contains(t, err.Error(), "invalid git branch name") + }, + }, + { + name: "push job with invalid path", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPush, + Repository: "test-repo", + Push: &provisioning.ExportJobOptions{ + Path: "../../../etc/passwd", // Invalid: path traversal + Message: "Test commit", + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.push.path") + }, + }, + { + name: "delete action without options", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.delete: Required value") + }, + }, + { + name: "delete action without paths or resources", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{}, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "at least one path or resource must be specified") + }, + }, + { + name: "valid delete action with paths", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"dashboard.json", "folder/other.json"}, + }, + }, + }, + wantErr: false, + }, + { + name: "valid delete action with resources", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{ + Resources: []provisioning.ResourceRef{ + { + Name: "my-dashboard", + Kind: "Dashboard", + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "delete action with invalid path", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"../../etc/passwd"}, // Invalid: path traversal + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.delete.paths[0]") + }, + }, + { + name: "delete action with resource missing name", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{ + Resources: []provisioning.ResourceRef{ + { + Kind: "Dashboard", + }, + }, + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.delete.resources[0].name") + }, + }, + { + name: "move action without options", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.move: Required value") + }, + }, + { + name: "move action without paths or resources", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + TargetPath: "new-location/", + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "at least one path or resource must be specified") + }, + }, + { + name: "move action without target path", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Paths: []string{"dashboard.json"}, + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.move.targetPath: Required value") + }, + }, + { + name: "valid move action", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Paths: []string{"old-location/dashboard.json"}, + TargetPath: "new-location/", + }, + }, + }, + wantErr: false, + }, + { + name: "move action with invalid target path", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Paths: []string{"dashboard.json"}, + TargetPath: "../../../etc/", // Invalid: path traversal + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.move.targetPath") + }, + }, + { + name: "valid migrate job", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMigrate, + Repository: "test-repo", + Migrate: &provisioning.MigrateJobOptions{ + History: true, + Message: "Migrate from legacy", + }, + }, + }, + wantErr: false, + }, + { + name: "migrate action without migrate options", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMigrate, + Repository: "test-repo", + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.migrate: Required value") + }, + }, + { + name: "valid pr job", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPullRequest, + Repository: "test-repo", + PullRequest: &provisioning.PullRequestJobOptions{ + PR: 123, + Ref: "refs/pull/123/head", + }, + }, + }, + wantErr: false, + }, + { + name: "delete action with resource missing kind", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{ + Resources: []provisioning.ResourceRef{ + { + Name: "my-dashboard", + }, + }, + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.delete.resources[0].kind") + }, + }, + { + name: "move action with valid resources", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Resources: []provisioning.ResourceRef{ + { + Name: "my-dashboard", + Kind: "Dashboard", + }, + }, + TargetPath: "new-location/", + }, + }, + }, + wantErr: false, + }, + { + name: "move action with resource missing kind", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Resources: []provisioning.ResourceRef{ + { + Name: "my-dashboard", + }, + }, + TargetPath: "new-location/", + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.move.resources[0].kind") + }, + }, + { + name: "move action with both paths and resources", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Paths: []string{"dashboard.json"}, + Resources: []provisioning.ResourceRef{ + { + Name: "my-dashboard", + Kind: "Dashboard", + }, + }, + TargetPath: "new-location/", + }, + }, + }, + wantErr: false, + }, + { + name: "move action with invalid source path", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Repository: "test-repo", + Move: &provisioning.MoveJobOptions{ + Paths: []string{"../invalid/path"}, + TargetPath: "valid/target/", + }, + }, + }, + wantErr: true, + validateError: func(t *testing.T, err error) { + require.Contains(t, err.Error(), "spec.move.paths[0]") + }, + }, + { + name: "delete action with both paths and resources", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Repository: "test-repo", + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"dashboard.json"}, + Resources: []provisioning.ResourceRef{ + { + Name: "my-dashboard", + Kind: "Dashboard", + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "push action with valid path", + job: &provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + }, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPush, + Repository: "test-repo", + Push: &provisioning.ExportJobOptions{ + Path: "some/valid/path", + Message: "Test commit", + }, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateJob(tt.job) + if tt.wantErr { + require.Error(t, err) + if tt.validateError != nil { + tt.validateError(t, err) + } + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/apps/shorturl/kinds/manifest.cue b/apps/shorturl/kinds/manifest.cue index 27519d040af..c078f591c81 100644 --- a/apps/shorturl/kinds/manifest.cue +++ b/apps/shorturl/kinds/manifest.cue @@ -1,68 +1,11 @@ package kinds manifest: { - // appName is the unique name of your app. It is used to reference the app from other config objects, - // and to generate the group used by your app in the app platform API. appName: "shorturl" groupOverride: "shorturl.grafana.app" - // groupOverride can be used to specify a non-appName-based API group. - // By default, an app's API group is LOWER(REPLACE(appName, '-', '')).ext.grafana.com, - // but there are cases where this needs to be changed. - // Keep in mind that changing this after an app is deployed can cause problems with clients and/or kind data. - // groupOverride: foo.ext.grafana.app - - // versions is a map of versions supported by your app. Version names should follow the format "v" or - // "v(alpha|beta)". Each version contains the kinds your app manages for that version. - // If your app needs access to kinds managed by another app, use permissions.accessKinds to allow your app access. versions: { - "v1alpha1": v1alpha1 - } - // extraPermissions contains any additional permissions your app may require to function. - // Your app will always have all permissions for each kind it manages (the items defined in 'kinds'). - extraPermissions: { - // If your app needs access to additional kinds supplied by other apps, you can list them here - accessKinds: [ - // Here is an example for your app accessing the playlist kind for reads and watch - // { - // group: "playlist.grafana.app" - // resource: "playlists" - // actions: ["get","list","watch"] - // } - ] + "v1beta1": { + kinds: [shorturl] + } } } - -// v1alpha1 is the v1alpha1 version of the app's API. -// It includes kinds which the v1alpha1 API serves, and (future) custom routes served globally from the v1alpha1 version. -v1alpha1: { - // kinds is the list of kinds served by this version - kinds: [shorturl] - // [OPTIONAL] - // served indicates whether this particular version is served by the API server. - // served should be set to false before a version is removed from the manifest entirely. - // served defaults to true if not present. - served: true - // [OPTIONAL] - // Codegen is a trait that tells the grafana-app-sdk, or other code generation tooling, how to process this kind. - // If not present, default values within the codegen trait are used. - // If you wish to specify codegen per-version, put this section in the version's object - // (for example, v1alpha1) instead. - codegen: { - // [OPTIONAL] - // ts contains TypeScript code generation properties for the kind - ts: { - // [OPTIONAL] - // enabled indicates whether the CLI should generate front-end TypeScript code for the kind. - // Defaults to true if not present. - enabled: true - } - // [OPTIONAL] - // go contains go code generation properties for the kind - go: { - // [OPTIONAL] - // enabled indicates whether the CLI should generate back-end go code for the kind. - // Defaults to true if not present. - enabled: true - } - } -} diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/constants.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/constants.go similarity index 91% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/constants.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/constants.go index e6000ff1f42..2fe732d2371 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/constants.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/constants.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import "k8s.io/apimachinery/pkg/runtime/schema" @@ -6,7 +6,7 @@ const ( // APIGroup is the API group used by all kinds in this package APIGroup = "shorturl.grafana.app" // APIVersion is the API version used by all kinds in this package - APIVersion = "v1alpha1" + APIVersion = "v1beta1" ) var ( diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_client_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_client_gen.go similarity index 99% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_client_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_client_gen.go index 1c2a81b3c34..6528fd89b98 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_client_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_client_gen.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import ( "context" diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_codec_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_codec_gen.go similarity index 97% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_codec_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_codec_gen.go index 15d7493bb80..37b343f5a53 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_codec_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_codec_gen.go @@ -2,7 +2,7 @@ // Code generated by grafana-app-sdk. DO NOT EDIT. // -package v1alpha1 +package v1beta1 import ( "encoding/json" diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_response_types_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_getgoto_response_types_gen.go similarity index 92% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_response_types_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_getgoto_response_types_gen.go index dfe47f8cbc1..c622afb824c 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_response_types_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_getgoto_response_types_gen.go @@ -1,6 +1,6 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 // +k8s:openapi-gen=true type GetGoto struct { diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_metadata_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_metadata_gen.go similarity index 98% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_metadata_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_metadata_gen.go index e7509c1a38e..bb2aa3dda44 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_metadata_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_metadata_gen.go @@ -1,6 +1,6 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 import ( time "time" diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_object_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_object_gen.go similarity index 99% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_object_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_object_gen.go index 734af74aae3..cfa817bc239 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_object_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_object_gen.go @@ -2,7 +2,7 @@ // Code generated by grafana-app-sdk. DO NOT EDIT. // -package v1alpha1 +package v1beta1 import ( "fmt" diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_schema_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_schema_gen.go similarity index 89% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_schema_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_schema_gen.go index 78f0d5839d2..2cebbc15437 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_schema_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_schema_gen.go @@ -2,7 +2,7 @@ // Code generated by grafana-app-sdk. DO NOT EDIT. // -package v1alpha1 +package v1beta1 import ( "github.com/grafana/grafana-app-sdk/resource" @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaShortURL = resource.NewSimpleSchema("shorturl.grafana.app", "v1alpha1", &ShortURL{}, &ShortURLList{}, resource.WithKind("ShortURL"), + schemaShortURL = resource.NewSimpleSchema("shorturl.grafana.app", "v1beta1", &ShortURL{}, &ShortURLList{}, resource.WithKind("ShortURL"), resource.WithPlural("shorturls"), resource.WithScope(resource.NamespacedScope)) kindShortURL = resource.Kind{ Schema: schemaShortURL, diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_spec_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_spec_gen.go similarity index 95% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_spec_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_spec_gen.go index 56a453e5b69..92b14036f7a 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_spec_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_spec_gen.go @@ -1,6 +1,6 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 // +k8s:openapi-gen=true type ShortURLSpec struct { diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_status_gen.go b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_status_gen.go similarity index 99% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_status_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_status_gen.go index cfbeb15b338..d8400bd7882 100644 --- a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_status_gen.go +++ b/apps/shorturl/pkg/apis/shorturl/v1beta1/shorturl_status_gen.go @@ -1,6 +1,6 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 // +k8s:openapi-gen=true type ShortURLstatusOperatorState struct { diff --git a/apps/shorturl/pkg/apis/shorturl_manifest.go b/apps/shorturl/pkg/apis/shorturl_manifest.go index 8cc2e9acf90..4e8acbe5797 100644 --- a/apps/shorturl/pkg/apis/shorturl_manifest.go +++ b/apps/shorturl/pkg/apis/shorturl_manifest.go @@ -16,22 +16,22 @@ import ( "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/validation/spec" - v1alpha1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" + v1beta1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1beta1" ) var ( - rawSchemaShortURLv1alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"ShortURL":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"path":{"description":"The original path to where the short url is linking too e.g. https://localhost:3000/eer8i1kictngga/new-dashboard-with-lib-panel","type":"string"}},"required":["path"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"lastSeenAt":{"description":"The last time the short URL was used, 0 is the initial value","type":"integer"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"required":["lastSeenAt"],"type":"object"}}`) - versionSchemaShortURLv1alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaShortURLv1alpha1, &versionSchemaShortURLv1alpha1) + rawSchemaShortURLv1beta1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"ShortURL":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"path":{"description":"The original path to where the short url is linking too e.g. https://localhost:3000/eer8i1kictngga/new-dashboard-with-lib-panel","type":"string"}},"required":["path"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"lastSeenAt":{"description":"The last time the short URL was used, 0 is the initial value","type":"integer"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"required":["lastSeenAt"],"type":"object"}}`) + versionSchemaShortURLv1beta1 app.VersionSchema + _ = json.Unmarshal(rawSchemaShortURLv1beta1, &versionSchemaShortURLv1beta1) ) var appManifestData = app.ManifestData{ AppName: "shorturl", Group: "shorturl.grafana.app", - PreferredVersion: "v1alpha1", + PreferredVersion: "v1beta1", Versions: []app.ManifestVersion{ { - Name: "v1alpha1", + Name: "v1beta1", Served: true, Kinds: []app.ManifestVersionKind{ { @@ -47,7 +47,7 @@ var appManifestData = app.ManifestData{ }, }, }, - Schema: &versionSchemaShortURLv1alpha1, + Schema: &versionSchemaShortURLv1beta1, Routes: map[string]spec3.PathProps{ "/goto": { Get: &spec3.Operation{ @@ -106,7 +106,7 @@ func RemoteManifest() app.Manifest { } var kindVersionToGoType = map[string]resource.Kind{ - "ShortURL/v1alpha1": v1alpha1.ShortURLKind(), + "ShortURL/v1beta1": v1beta1.ShortURLKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. @@ -117,7 +117,7 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist } var customRouteToGoResponseType = map[string]any{ - "v1alpha1|ShortURL|goto|GET": v1alpha1.GetGoto{}, + "v1beta1|ShortURL|goto|GET": v1beta1.GetGoto{}, } // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. diff --git a/apps/shorturl/pkg/app/app.go b/apps/shorturl/pkg/app/app.go index bb97db78a93..dc2e604aba3 100644 --- a/apps/shorturl/pkg/app/app.go +++ b/apps/shorturl/pkg/app/app.go @@ -18,7 +18,7 @@ import ( "github.com/grafana/grafana-app-sdk/operator" "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana-app-sdk/simple" - shorturlv1alpha1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" + shorturlv1beta1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" ) @@ -31,11 +31,11 @@ var ( func New(cfg app.Config) (app.App, error) { cfg.KubeConfig.APIPath = "apis" tmp, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()). - ClientFor(shorturlv1alpha1.ShortURLKind()) + ClientFor(shorturlv1beta1.ShortURLKind()) if err != nil { return nil, fmt.Errorf("unable to create client") } - client := shorturlv1alpha1.NewShortURLClient(tmp) + client := shorturlv1beta1.NewShortURLClient(tmp) simpleConfig := simple.AppConfig{ Name: "shorturl", @@ -49,11 +49,11 @@ func New(cfg app.Config) (app.App, error) { }, ManagedKinds: []simple.AppManagedKind{ { - Kind: shorturlv1alpha1.ShortURLKind(), + Kind: shorturlv1beta1.ShortURLKind(), Validator: &simple.Validator{ ValidateFunc: func(ctx context.Context, req *app.AdmissionRequest) error { // Cast the incoming object to ShortURL for validation - shortURL, ok := req.Object.(*shorturlv1alpha1.ShortURL) + shortURL, ok := req.Object.(*shorturlv1beta1.ShortURL) if !ok { return fmt.Errorf("expected ShortURL object, got %T", req.Object) } @@ -105,7 +105,7 @@ func New(cfg app.Config) (app.App, error) { url = url + "/" + info.Spec.Path if req.URL.Query().Get("redirect") == "false" { // helpful for testing - return json.NewEncoder(w).Encode(shorturlv1alpha1.GetGoto{ + return json.NewEncoder(w).Encode(shorturlv1beta1.GetGoto{ Url: url, }) } @@ -133,10 +133,10 @@ func New(cfg app.Config) (app.App, error) { func GetKinds() map[schema.GroupVersion][]resource.Kind { gv := schema.GroupVersion{ - Group: shorturlv1alpha1.ShortURLKind().Group(), - Version: shorturlv1alpha1.ShortURLKind().Version(), + Group: shorturlv1beta1.ShortURLKind().Group(), + Version: shorturlv1beta1.ShortURLKind().Version(), } return map[schema.GroupVersion][]resource.Kind{ - gv: {shorturlv1alpha1.ShortURLKind()}, + gv: {shorturlv1beta1.ShortURLKind()}, } } diff --git a/apps/shorturl/plugin/src/generated/shorturl/v1alpha1/shorturl_object_gen.ts b/apps/shorturl/plugin/src/generated/shorturl/v1beta1/shorturl_object_gen.ts similarity index 100% rename from apps/shorturl/plugin/src/generated/shorturl/v1alpha1/shorturl_object_gen.ts rename to apps/shorturl/plugin/src/generated/shorturl/v1beta1/shorturl_object_gen.ts diff --git a/apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.metadata.gen.ts b/apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.metadata.gen.ts new file mode 100644 index 00000000000..4377f3c1d08 --- /dev/null +++ b/apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.metadata.gen.ts @@ -0,0 +1,30 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +export interface Metadata { + updateTimestamp: string; + createdBy: string; + uid: string; + creationTimestamp: string; + deletionTimestamp?: string; + finalizers: string[]; + resourceVersion: string; + generation: number; + updatedBy: string; + labels: Record; +} + +export const defaultMetadata = (): Metadata => ({ + updateTimestamp: "", + createdBy: "", + uid: "", + creationTimestamp: "", + finalizers: [], + resourceVersion: "", + generation: 0, + updatedBy: "", + labels: {}, +}); + diff --git a/apps/shorturl/plugin/src/generated/shorturl/v1alpha1/types.spec.gen.ts b/apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.spec.gen.ts similarity index 100% rename from apps/shorturl/plugin/src/generated/shorturl/v1alpha1/types.spec.gen.ts rename to apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.spec.gen.ts diff --git a/apps/shorturl/plugin/src/generated/shorturl/v1alpha1/types.status.gen.ts b/apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.status.gen.ts similarity index 100% rename from apps/shorturl/plugin/src/generated/shorturl/v1alpha1/types.status.gen.ts rename to apps/shorturl/plugin/src/generated/shorturl/v1beta1/types.status.gen.ts diff --git a/conf/defaults.ini b/conf/defaults.ini index 61d0ca7e318..f59a5dbeb44 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -113,7 +113,7 @@ network = "tcp" address = "127.0.0.1:10000" use_tls = false cert_file = -key_file = +cert_key = # this will log the request and response for each unary gRPC call enable_logging = false @@ -340,6 +340,10 @@ application_insights_connection_string = # Optional. Specifies an Application Insights endpoint URL where the endpoint string is wrapped in backticks ``. application_insights_endpoint_url = +# Optional, defaults to true. Configure automatic route tracking for single page applications in Application Insights. +# See https://learn.microsoft.com/en-us/azure/azure-monitor/app/application-insights-faq#is-there-a-way-to-see-fewer-events-per-transaction-when-i-use-the-application-insights-javascript-sdk +application_insights_auto_route_tracking = true + # Controls if the UI contains any links to user feedback forms feedback_links_enabled = true @@ -443,7 +447,6 @@ content_security_policy_report_only_template = """script-src 'self' 'unsafe-eval csrf_always_check = false # Comma-separated list of plugins ids that will be loaded inside the frontend sandbox -# Currently behind the feature flag pluginsFrontendSandbox enable_frontend_sandbox_for_plugins = # Comma-separated list of paths for POST/PUT URL in actions. Empty will allow anything that is not on the same origin @@ -1935,6 +1938,9 @@ preinstall_disabled = false # Update strategy for plugins. # Available options: "latest", "minor" update_strategy = minor +# Enable automatic updates for preinstalled plugins on startup. +# When enabled, preinstalled plugins without a pinned version will be updated to the latest version. +preinstall_auto_update = true #################################### Grafana Live ########################################## [live] diff --git a/conf/sample.ini b/conf/sample.ini index bee580aaa98..8fcce01b2fc 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -114,7 +114,7 @@ ;address = "127.0.0.1:10000" ;use_tls = false ;cert_file = -;key_file = +;cert_key = ;max_recv_msg_size = ;max_send_msg_size = # this will log the request and response for each unary gRPC call @@ -333,6 +333,10 @@ # Optional. Specifies an Application Insights endpoint URL where the endpoint string is wrapped in backticks ``. ;application_insights_endpoint_url = +# Optional, defaults to true. Configure automatic route tracking for single page applications in Application Insights. +# See https://learn.microsoft.com/en-us/azure/azure-monitor/app/application-insights-faq#is-there-a-way-to-see-fewer-events-per-transaction-when-i-use-the-application-insights-javascript-sdk +;application_insights_auto_route_tracking = + # Controls if the UI contains any links to user feedback forms ;feedback_links_enabled = true @@ -442,7 +446,6 @@ ;csrf_always_check = false # Comma-separated list of plugins ids that will be loaded inside the frontend sandbox -# Currently behind the feature flag pluginsFrontendSandbox ;enable_frontend_sandbox_for_plugins = # Comma-separated list of paths for POST/PUT URL in actions. Empty will allow anything that is not on the same origin diff --git a/devenv/dev-dashboards/annotations/multi-lane-annotations.json b/devenv/dev-dashboards/annotations/multi-lane-annotations.json new file mode 100644 index 00000000000..edd9c6ef757 --- /dev/null +++ b/devenv/dev-dashboards/annotations/multi-lane-annotations.json @@ -0,0 +1,1020 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "maxDataPoints": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "noise": 10, + "refId": "A", + "scenarioId": "random_walk", + "spread": 10 + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\",\n \"Milestones\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\",\n \"#F2495C\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1720697881000,\n 1728580067000,\n 1730129182000,\n 1730734644000,\n 1732542118000,\n 1736426576000,\n 1736874473000,\n 1738676647000,\n 1740067713000,\n 1740684246000,\n 1743733609000,\n 1744034815000,\n 1745335229000,\n 1745936130000,\n 1746107356000,\n 1747055303000,\n 1747946736000,\n 1748446531000,\n 1750098297000,\n 1750963080000,\n 1750963096000,\n 1752067555000,\n 1752080606000,\n 1753125884000,\n 1754395568000,\n 1754407010000,\n 1756228450000,\n 1757947221000,\n 1759763050000,\n 1759924632000,\n 1761572602000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"timeEnd\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1729081505000,\n 1730129307000,\n 1730838127000,\n 1732543887000,\n 1736528756000,\n 1736874600000,\n 1740064375000,\n 1740170732000,\n 1742397235000,\n 1743733611000,\n 1744038997000,\n 1745341972000,\n 1745936141000,\n 1746118905000,\n 1747055307000,\n 1747853613000,\n 1748446520000,\n 1750187872000,\n 1750963071000,\n 1751038503000,\n 1752067563000,\n 1752166243000,\n 1753396603000,\n 1754395592000,\n 1754407004000,\n 1756228445000,\n 1758134868000,\n 1759763044000,\n 1761572592000,\n 1761572593000,\n null\n ],\n \"type\": \"number\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"0.1.0\",\n \"1.0.2\",\n \"1.0.3\",\n \"1.0.4\",\n \"1.0.5\",\n \"1.0.6\",\n \"1.0.7\",\n \"1.0.8\",\n \"1.0.9\",\n \"1.0.10\",\n \"1.0.11\",\n \"1.0.12\",\n \"1.0.13\",\n \"1.0.14\",\n \"1.0.15\",\n \"1.0.16\",\n \"1.0.17\",\n \"1.0.18\",\n \"1.0.19\",\n \"1.0.20\",\n \"1.0.21\",\n \"1.0.22\",\n \"1.0.23\",\n \"1.0.24\",\n \"1.0.25\",\n \"1.0.26\",\n \"1.0.27\",\n \"1.0.28\",\n \"1.0.29\",\n \"1.0.30\",\n \"1.0.31\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n false\n ],\n \"type\": \"boolean\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"red\",\n \"mappings\": {\n \"text\": {\n \"source\": \"field\",\n \"value\": \"closed\"\n },\n \"time\": {\n \"source\": \"field\",\n \"value\": \"created_at\"\n },\n \"timeEnd\": {\n \"source\": \"field\",\n \"value\": \"closed_at\"\n },\n \"title\": {\n \"source\": \"field\",\n \"value\": \"title\"\n }\n },\n \"name\": \"Milestones\",\n \"target\": {\n \"options\": {\n \"query\": \"\"\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Milestones\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n true,\n false\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 31,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n },\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Issues\",\n \"Issues\",\n \"Issues\",\n \"Issues\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#FADE2A\",\n \"#FADE2A\",\n \"#FADE2A\",\n \"#FADE2A\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760112753000,\n 1759930212000,\n 1759924899000,\n 1759850404000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"timeEnd\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760972421000,\n null,\n null,\n 1759926839000\n ],\n \"type\": \"number\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"[BUG]: Error when data source configured for multi tenant queries\",\n \"[FEAT]: Allow users without explore permissions to use logs drilldown\",\n \"Sync Logs panel and label/fields empty states\",\n \"fix: labels clear variable state not showing\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"[BUG]: Error when data source configured for multi tenant queries\",\n \"[FEAT]: Allow users without explore permissions to use logs drilldown\",\n \"Sync Logs panel and label/fields empty states\",\n \"fix: labels clear variable state not showing\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"tags\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n [\n \"bug\",\n \"needs-triage\"\n ],\n [\n \"enhancement\",\n \"needs-triage\"\n ],\n [\n \"needs-triage\"\n ],\n [\n \"bug\"\n ]\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"yellow\",\n \"mappings\": {\n \"tags\": {\n \"source\": \"field\",\n \"value\": \"labels\"\n },\n \"time\": {\n \"source\": \"field\",\n \"value\": \"created_at\"\n },\n \"timeEnd\": {\n \"source\": \"field\",\n \"value\": \"closed_at\"\n }\n },\n \"name\": \"Issues\",\n \"target\": {\n \"options\": {\n \"query\": \"\"\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Issues\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n false,\n false,\n true\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 4,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n },\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Pulls\",\n \"Pulls\",\n \"Pulls\",\n \"Pulls\",\n \"Pulls\",\n \"Pulls\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\",\n \"#5794F2\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1759941908000,\n 1759854730000,\n 1759852733000,\n 1759841416000,\n 1759786844000,\n 1759762979000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"timeEnd\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760110687000,\n 1759857566000,\n 1759926838000,\n 1759843529000,\n 1759848237000,\n 1759769543000\n ],\n \"type\": \"number\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"feat(EmptyLogs): add button to fix with assistant\",\n \"docs: Update troubleshooting page\",\n \"fix: unexpected clear variable behavior\",\n \"docs: update stale readme, fix docker install script\",\n \"docs: Update install and troubleshooting\",\n \"fix: fix RegExp.source removing flags, use toString instead\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"feat(EmptyLogs): add button to fix with assistant\",\n \"docs: Update troubleshooting page\",\n \"fix: unexpected clear variable behavior\",\n \"docs: update stale readme, fix docker install script\",\n \"docs: Update install and troubleshooting\",\n \"fix: fix RegExp.source removing flags, use toString instead\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"blue\",\n \"mappings\": {\n \"time\": {\n \"source\": \"field\",\n \"value\": \"created_at\"\n },\n \"timeEnd\": {\n \"source\": \"field\",\n \"value\": \"merged_at\"\n }\n },\n \"name\": \"Pulls\",\n \"target\": {\n \"options\": {\n \"query\": \"\",\n \"timeField\": 1\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Pull_Requests\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n true,\n true,\n true,\n true,\n true,\n true\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 6,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n },\n {\n \"fields\": [\n {\n \"name\": \"type\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\",\n \"Commits\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"color\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\",\n \"#73BF69\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"time\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n 1760349982000,\n 1760110687000,\n 1759950041000,\n 1759948586000,\n 1759933371000,\n 1759926838000,\n 1759857566000,\n 1759850172000,\n 1759848237000,\n 1759843529000,\n 1759769543000,\n 1759762725000\n ],\n \"type\": \"time\"\n },\n {\n \"name\": \"title\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"Matias Chomicki\",\n \"Matias Chomicki\",\n \"github-actions[bot]\",\n \"Liza Detrick\",\n \"grafana-plugins-platform-bot[bot]\",\n \"Galen Kistler\",\n \"J Stickler\",\n \"Piotr Jamróz\",\n \"J Stickler\",\n \"Galen Kistler\",\n \"Galen Kistler\",\n \"Galen Kistler\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"text\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"feat(LogsListScene): add defaultDisplayedFields support (#1554)\\n\\n* feat(LogsListScene): add defaultDisplayedFields support\\n\\n* chore: update mock\\n\\n* fix(Table): omit unsupported field\\n\\n* fix(LogListScene): reset default displayed fields when switching viz\\n\\n* chore: lint\",\n \"feat(EmptyLogs): add button to fix with assistant (#1571)\\n\\n* feat(LogListScene): add error type to identify empty results\\n\\n* feat(LogsPanelError): use assistant to investigate no results\\n\\n* fix(Shoo): shoo\\n\\n* feat(EmbeddedLogs): customize empty logs prompt\\n\\n* feat(LogsPanelError): implement custom CTA\\n\\n* chore: spelling\\n\\n* chore: add unit test\\n\\n* chore: remove it.only\\n\\n* fix(variableHelpers): exclude primary label from variables to clear\\n\\n* feat(embedding): refactor options\\n\\n* feat(assistant): create service\\n\\n* feat(assistant): integrate with empty layout and no matching labels\\n\\n* chore: fix types\\n\\n* chore: update tests\\n\\n* chore: remove quotes\\n\\n* chore: prevent access to an undefined object\\n\\n* chore: move label to constants\\n\\n* Revert \\\"chore: move label to constants\\\"\\n\\nThis reverts commit fbdf96415e5b2af7a5cae4eec6e6d5a30bc05d1c.\\n\\n* chore: use name instead of label\\n\\n* Revert \\\"fix(Shoo): shoo\\\"\\n\\nThis reverts commit 75993f7a845b514cb5da9667e94a2f0a3c913f24.\\n\\n* chore: more unit tests\\n\\n* test(variableHelpers): test getVariablesThatCanBeCleared\",\n \"chore: bump @grafana/create-plugin configuration to 5.26.9 (#1559)\",\n \"feat(table): preferences (#1534)\",\n \"chore(version): bump version to v1.0.29\",\n \"fix: unexpected clear variable behavior (#1567)\\n\\n* fix: show clear ui when query contains clearable label filters\",\n \"docs: Update troubleshooting page (#1568)\",\n \"fix: validate primary label correctly (#1561)\\n\\n* fix: properly clear variables for legacy urls\",\n \"docs: Update install and troubleshooting (#1564)\\n\\nCo-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com>\",\n \"docs: update stale readme, fix docker install script (#1565)\\n\\n* docs: update stale readme\\n\\n* chore: fix docker script\",\n \"fix: fix RegExp.source removing flags, use toString instead (#1563)\\n\\n* fix: fix RegExp.source removing flags, use toString instead\",\n \"fix: stale urls (#1562)\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"id\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n \"cf08043276597abe16f4ae9dbe97bd988d31b7ed\",\n \"01e343dacde3b9fcd862ec2cb2b04fbea4eb6d63\",\n \"9e67734938b980a80496d4cdc60efe7dbc4ba74e\",\n \"924ebd235b6ea99d297d8e04e08ac8ea90a32968\",\n \"23b9b5dc0504a4c397b326562cb796cfdc0531df\",\n \"089fdfa26c7daad5543be1aae2377850348663c3\",\n \"437a568a7a7279c436a674c7b1b0d11a4971d47b\",\n \"ee12a20b32db26b82fe32ed9d5868bd2dde4a4d0\",\n \"657880b399682101616b3413e04c2b664d018ef3\",\n \"fc776d767ebf2a64fb9fad7ab0f479cdaf536cdb\",\n \"a9207d235af9b2f5a4915f072801128a41c4063b\",\n \"e2eae8b8f45dc685659689f85962b0f0a58f13b1\"\n ],\n \"type\": \"string\"\n },\n {\n \"name\": \"source\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n {\n \"datasource\": {\n \"type\": \"grafana-github-datasource\",\n \"uid\": \"feyypehpl45xcf\"\n },\n \"enable\": true,\n \"hide\": false,\n \"iconColor\": \"green\",\n \"mappings\": {\n \"text\": {\n \"source\": \"field\",\n \"value\": \"message\"\n },\n \"time\": {\n \"source\": \"field\",\n \"value\": \"committed_at\"\n },\n \"title\": {\n \"source\": \"field\",\n \"value\": \"author\"\n }\n },\n \"name\": \"Commits\",\n \"target\": {\n \"options\": {\n \"gitRef\": \"main\"\n },\n \"owner\": \"grafana\",\n \"queryType\": \"Commits\",\n \"refId\": \"Anno\",\n \"repository\": \"logs-drilldown\"\n }\n },\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null,\n null\n ],\n \"type\": \"other\"\n },\n {\n \"name\": \"isRegion\",\n \"config\": {\n \"custom\": {}\n },\n \"values\": [\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false,\n false\n ],\n \"type\": \"boolean\"\n }\n ],\n \"length\": 12,\n \"meta\": {\n \"dataTopic\": \"annotations\"\n }\n }\n]", + "refId": "B", + "scenarioId": "raw_frame" + } + ], + "title": "Time series", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "annotations": { + "multiLane": true + }, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Time series (multi-lane)", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "candleStyle": "candles", + "colorStrategy": "open-close", + "colors": { + "down": "red", + "up": "green" + }, + "includeAllFields": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mode": "candles+volume", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Candlestick", + "type": "candlestick" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "annotations": { + "multiLane": true + }, + "candleStyle": "candles", + "colorStrategy": "open-close", + "colors": { + "down": "red", + "up": "green" + }, + "includeAllFields": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mode": "candles+volume", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Candlestick (multi-lane)", + "type": "candlestick" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "State timeline", + "type": "state-timeline" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 6, + "options": { + "alignValue": "left", + "annotations": { + "multiLane": true + }, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "State timeline (multi-lane)", + "type": "state-timeline" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 7, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Heatmap", + "type": "heatmap" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 8, + "options": { + "annotations": { + "multiLane": true + }, + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Heatmap (multi-lane)", + "type": "heatmap" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 9, + "options": { + "colWidth": 0.9, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Status history", + "type": "status-history" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 10, + "options": { + "annotations": { + "multiLane": true + }, + "colWidth": 0.9, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 1, + "refId": "A" + } + ], + "title": "Status history (multi-lane)", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 40 + }, + "id": 11, + "maxDataPoints": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"type\": \"timeseries\"\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"config\": {\n \"interval\": 604800000\n }\n },\n {\n \"name\": \"A-series\",\n \"type\": \"number\",\n \"labels\": {},\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1755870318505,\n 1756475118505,\n 1757079918505,\n 1757684718505,\n 1758289518505,\n 1758894318505,\n 1759499118505,\n 1760103918505,\n 1760708718505,\n 1761313518505\n ],\n [\n 49.57457814271496,\n 64.78808691382616,\n 94.88860442042386,\n 96.59132232810856,\n 58.57144477681538,\n 79.33618638515327,\n 89.64117713117561,\n 134.51905322565585,\n 122.83710544843791,\n 79.84039369237018\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"B\",\n \"name\": \"xymark\",\n \"meta\": {\n \"dataTopic\": \"annotations\"\n },\n \"fields\": [\n {\n \"name\": \"xMin\",\n \"type\": \"time\",\n \"config\": {}\n },\n {\n \"name\": \"xMax\",\n \"type\": \"time\",\n \"config\": {}\n },\n {\n \"name\": \"yMin\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"yMax\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"color\",\n \"type\": \"string\",\n \"config\": {}\n },\n {\n \"name\": \"fillOpacity\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"lineWidth\",\n \"type\": \"number\",\n \"config\": {}\n },\n {\n \"name\": \"lineStyle\",\n \"type\": \"string\",\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1757684718505\n ],\n [\n 1758894318505\n ],\n [\n 70\n ],\n [\n 120\n ],\n [\n \"#f00\"\n ],\n [\n 0.1\n ],\n [\n 1\n ],\n [\n \"dash\"\n ]\n ]\n }\n }\n]", + "refId": "B", + "scenarioId": "raw_frame" + } + ], + "title": "xymark", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "tags": ["gdev", "panel-tests", "graph-ng"], + "templating": { + "list": [] + }, + "time": { + "from": "2025-08-22T13:45:18.505Z", + "to": "2025-10-28T04:38:37.130Z" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Multi-lane annotations", + "uid": "ad7p5pj", + "version": 17 +} diff --git a/devenv/dev-dashboards/panel-status-history/status-history-thresholds-mappings.json b/devenv/dev-dashboards/panel-status-history/status-history-thresholds-mappings.json new file mode 100644 index 00000000000..44d4dda9522 --- /dev/null +++ b/devenv/dev-dashboards/panel-status-history/status-history-thresholds-mappings.json @@ -0,0 +1,1159 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 15116, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 11, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "title": "default", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 10 + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "#6ED0E0", + "value": 30 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 2, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "default absolute thresholds", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 10 + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "#6ED0E0", + "value": 30 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 8, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "default percentage thresholds", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 10 + }, + { + "color": "#EAB839", + "value": 20 + }, + { + "color": "#6ED0E0", + "value": 30 + } + ] + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 3, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "override thresholds", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [ + { + "options": { + "from": 0, + "result": { + "color": "green", + "index": 0 + }, + "to": 9.9999 + }, + "type": "range" + }, + { + "options": { + "from": 10, + "result": { + "color": "yellow", + "index": 1 + }, + "to": 14.9999 + }, + "type": "range" + }, + { + "options": { + "from": 15, + "result": { + "color": "red", + "index": 2 + }, + "to": 24.9999 + }, + "type": "range" + }, + { + "options": { + "from": 25, + "result": { + "color": "blue", + "index": 3 + }, + "to": 100000 + }, + "type": "range" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 6, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.47, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "default value mappings", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "from": 0, + "result": { + "color": "green", + "index": 0 + }, + "to": 9.9999 + }, + "type": "range" + }, + { + "options": { + "from": 10, + "result": { + "color": "yellow", + "index": 1 + }, + "to": 14.9999 + }, + "type": "range" + }, + { + "options": { + "from": 15, + "result": { + "color": "red", + "index": 2 + }, + "to": 24.9999 + }, + "type": "range" + }, + { + "options": { + "from": 25, + "result": { + "color": "blue", + "index": 3 + }, + "to": 100000 + }, + "type": "range" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 7, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + } + ], + "title": "override value mappings", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 5, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 10\n },\n {\n \"color\": \"red\",\n \"value\": 15\n },\n {\n \"color\": \"#6ED0E0\",\n \"value\": 25\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000\n ],\n [\n 5,\n 10,\n 20,\n 30\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "field thresholds from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 9 + }, + "id": 9, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "5,10,20,30,40" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "hide": false, + "max": 30, + "min": 0.01, + "noise": 30, + "refId": "B", + "scenarioId": "random_walk", + "startValue": 1 + } + ], + "title": "threshold from random walk", + "transformations": [ + { + "id": "configFromData", + "options": { + "configRefId": "B", + "mappings": [ + { + "fieldName": "B-series", + "handlerKey": "threshold1" + } + ] + } + } + ], + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "color": "purple", + "index": 0, + "text": "null" + } + }, + "type": "special" + }, + { + "options": { + "match": "nan", + "result": { + "color": "red", + "index": 1, + "text": "NaN" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 18 + }, + "id": 12, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000\n ],\n [\n 5,\n null,\n 20,\n null,\n 40\n ]\n ],\n \"entities\": [null, { \"NaN\": [3]}]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "special null | NaN value mapping from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "match": "null+nan", + "result": { + "color": "super-light-red", + "index": 0, + "text": "null + NaN" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 18 + }, + "id": 13, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000\n ],\n [\n 5,\n null,\n 20,\n null,\n 40\n ]\n ],\n \"entities\": [null, { \"NaN\": [1]}]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "special null + NaN value mapping from data", + "type": "status-history" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "match": "false", + "result": { + "color": "red", + "index": 0 + } + }, + "type": "special" + }, + { + "options": { + "match": "null+nan", + "result": { + "color": "blue", + "index": 1, + "text": "null + NaN" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 18 + }, + "id": 14, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000,\n 1674750835000,\n 1674754235000,\n 1674757835000\n ],\n [\n null,\n null,\n false,\n true,\n true,\n false,\n true,\n null\n ]\n ],\n \"entities\": [null, { \"NaN\": [0], \"Undefined\": [1]}]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "boolean values from data", + "type": "status-history" + }, + { + "id": 15, + "type": "status-history", + "title": "no data", + "gridPos": { + "x": 18, + "y": 18, + "h": 9, + "w": 6 + }, + "fieldConfig": { + "defaults": { + "custom": { + "lineWidth": 0, + "fillOpacity": 70, + "spanNulls": false, + "insertNulls": false, + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "axisPlacement": "auto" + }, + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "fieldMinMax": false + }, + "overrides": [] + }, + "pluginVersion": "12.3.0-pre", + "targets": [ + { + "scenarioId": "random_walk", + "refId": "A", + "seriesCount": 0 + } + ], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "options": { + "mergeValues": true, + "showValue": "auto", + "alignValue": "center", + "rowHeight": 0.9, + "legend": { + "showLegend": true, + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none", + "hideZeros": false + } + } + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": ["gdev", "panel-tests", "state-timeline", "graph-ng"], + "templating": { + "list": [] + }, + "time": { + "from": "2023-01-26T11:29:47.180Z", + "to": "2023-01-26T16:29:39.205Z" + }, + "timepicker": {}, + "timezone": "utc", + "title": "StatusHistory - Thresholds & Mappings", + "uid": "a2f4ad9e-3b44-4624-8067-35f31be5d309", + "version": 1 +} diff --git a/devenv/frontend-service/configs/nginx.conf b/devenv/frontend-service/configs/nginx.conf index bb136f695aa..f052bb52d17 100644 --- a/devenv/frontend-service/configs/nginx.conf +++ b/devenv/frontend-service/configs/nginx.conf @@ -30,7 +30,7 @@ server { server_name _; otel_trace on; - otel_trace_context inject; + otel_trace_context propagate; otel_span_name "$request_method Request"; location ~ ^/-/down/?$ { diff --git a/devenv/frontend-service/docker-compose.yaml b/devenv/frontend-service/docker-compose.yaml index 0d65b27c250..cce40084189 100644 --- a/devenv/frontend-service/docker-compose.yaml +++ b/devenv/frontend-service/docker-compose.yaml @@ -75,7 +75,7 @@ services: GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION: jaeger,w3c postgres: - image: postgres:16.1-alpine3.19 + image: postgres:16.1-alpine3.19@sha256:17eb369d9330fe7fbdb2f705418c18823d66322584c77c2b43cc0e1851d01de7 environment: POSTGRES_USER: grafana POSTGRES_PASSWORD: grafana @@ -86,7 +86,7 @@ services: - 'alloy.logs=true' alloy: - image: grafana/alloy:latest + image: grafana/alloy:v1.11.3@sha256:8c7256f412feb9f5f48f9f6f9394dc97ca887f63dea9304f347970ecc1787669 volumes: - ./configs/alloy:/alloy-config - /var/run/docker.sock:/var/run/docker.sock # To scrape Docker container logs @@ -104,7 +104,7 @@ services: - 'alloy.logs=true' prometheus: - image: prom/prometheus + image: prom/prometheus:v3.7.3@sha256:49214755b6153f90a597adcbff0252cc61069f8ab69ce8411285cd4a560e8038 volumes: - prometheus-data:/prometheus command: @@ -116,7 +116,7 @@ services: - 'alloy.logs=true' loki: - image: grafana/loki + image: grafana/loki:3.5.7@sha256:0eaee7bf39cc83aaef46914fb58f287d4f4c4be6ec96b86c2ed55719a75e49c8 volumes: - loki-data:/loki command: -config.file=/etc/loki/local-config.yaml @@ -124,7 +124,7 @@ services: - 'alloy.logs=true' tempo-init: - image: busybox + image: busybox:1.37.0@sha256:e3652a00a2fabd16ce889f0aa32c38eec347b997e73bd09e69c962ec7f8732ee user: root entrypoint: - 'chown' @@ -134,7 +134,7 @@ services: - tempo-data:/var/tempo tempo: - image: grafana/tempo + image: grafana/tempo:2.9.0@sha256:65a5789759435f1ef696f1953258b9bbdb18eb571d5ce711ff812d2e128288a4 volumes: - tempo-data:/var/lib/tempo - ./configs/tempo.yaml:/etc/tempo/tempo.yaml diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet index 80be668ecc4..cac117cd4e7 100644 --- a/devenv/jsonnet/dev-dashboards.libsonnet +++ b/devenv/jsonnet/dev-dashboards.libsonnet @@ -74,6 +74,7 @@ "mostly-blank-dashboard": (import '../dev-dashboards/scenarios/mostly-blank-dashboard.json'), "mssql_fakedata": (import '../dev-dashboards/datasource-mssql/mssql_fakedata.json'), "mssql_unittest": (import '../dev-dashboards/datasource-mssql/mssql_unittest.json'), + "multi-lane-annotations": (import '../dev-dashboards/annotations/multi-lane-annotations.json'), "mysql_fakedata": (import '../dev-dashboards/datasource-mysql/mysql_fakedata.json'), "mysql_unittest": (import '../dev-dashboards/datasource-mysql/mysql_unittest.json'), "new_features_in_v74": (import '../dev-dashboards/datasource-testdata/new_features_in_v74.json'), @@ -96,6 +97,7 @@ "rows-to-fields": (import '../dev-dashboards/transforms/rows-to-fields.json'), "shared_queries": (import '../dev-dashboards/panel-common/shared_queries.json'), "slow_queries_and_annotations": (import '../dev-dashboards/scenarios/slow_queries_and_annotations.json'), + "status-history-thresholds-mappings": (import '../dev-dashboards/panel-status-history/status-history-thresholds-mappings.json'), "table_footer": (import '../dev-dashboards/panel-table/table_footer.json'), "table_kitchen_sink": (import '../dev-dashboards/panel-table/table_kitchen_sink.json'), "table_markdown": (import '../dev-dashboards/panel-table/table_markdown.json'), diff --git a/devenv/scopes/README.md b/devenv/scopes/README.md new file mode 100644 index 00000000000..3607b70d3eb --- /dev/null +++ b/devenv/scopes/README.md @@ -0,0 +1,140 @@ +# Scopes Provisioning Script + +This script generates Scopes, ScopeNodes, and ScopeNavigations for Grafana development environments. + +## Usage + +### Create resources + +```bash +# From devenv directory +./setup.sh scopes + +# Or run directly +cd scopes +go run scopes.go +``` + +### Delete all gdev-prefixed resources + +```bash +# From devenv directory +./setup.sh undev + +# Or run directly +cd scopes +go run scopes.go -clean +``` + +**Note about caching**: The `/find/scope_navigations` endpoint used by the UI caches ScopeNavigation results for 15 minutes. After running cleanup, deleted resources may still appear in the UI until the cache expires. The resources are actually deleted (you can verify by checking the `/scopenavigations` list endpoint), but the UI will refresh after ~15 minutes or after restarting Grafana. + +Doing an `Empty Cache and Hard Reload` will also help. + +## Configuration + +The script reads from `scopes-config.yaml` by default. You can specify a different config file: + +```bash +go run scopes.go -config=my-config.yaml +``` + +### Configuration Format + +The configuration file uses YAML format with a natural tree structure. The indentation itself represents the hierarchy: + +- **scopes**: Map of scope definitions (key is the scope name) +- **tree**: Tree structure of scope nodes where the YAML structure defines parent-child relationships +- **navigations**: Map of scope navigations linking URLs to scopes (key is the navigation name) + +Example: + +```yaml +scopes: + app1: + title: Application 1 + filters: + - key: app + operator: equals + value: app1 + +tree: + environments: + title: Environments + nodeType: container + children: + production: + title: Production + nodeType: container + children: + app1-prod: + title: Application 1 + nodeType: leaf + linkId: app1 + linkType: scope + +navigations: + # Link to a dashboard + app1-nav: + url: /d/86Js1xRmk + scope: app1 + + # Link to another dashboard + app2-nav: + url: /d/GlAqcPgmz + scope: app2 + + # Custom URLs + explore-nav: + url: /explore + scope: app1 +``` + +### Tree Structure + +The tree structure uses YAML's natural indentation to represent hierarchy: + +- **Key**: Unique identifier for the node (will be prefixed with "gdev-") +- **title**: Display title +- **nodeType**: Either "container" (can have children) or "leaf" (selectable scope) +- **linkId**: References a scope name (if nodeType is "leaf") +- **linkType**: Usually "scope" +- **children**: Map of child nodes (nested structure follows YAML indentation) + +### Node Types + +- **container**: A category/grouping node that can contain other nodes +- **leaf**: A selectable node that links to a scope + +### Navigations + +Navigations link URLs to scopes. The `url` field should contain the full URL path (e.g., `/d/abc123` for dashboards or `/explore` for other pages). + +To find dashboard UIDs from gdev dashboards: + +```bash +# Find UIDs of all gdev dashboards +find devenv/dev-dashboards -name "*.json" -exec sh -c 'echo "{}:" && jq -r ".uid // .dashboard.uid // \"NO_UID\"" {}' \; + +# Or for a specific dashboard +jq -r ".uid // .dashboard.uid" devenv/dev-dashboards/all-panels.json +``` + +## Environment Variables + +- `GRAFANA_URL`: Grafana URL (default: http://localhost:3000) +- `GRAFANA_NAMESPACE`: Namespace (default: default) +- `GRAFANA_USER`: Grafana username (default: admin) +- `GRAFANA_PASSWORD`: Grafana password (default: admin) + +## Command Line Flags + +- `-url`: Grafana URL +- `-namespace`: Namespace +- `-config`: Config file path (default: scopes-config.yaml) +- `-user`: Grafana username +- `-password`: Grafana password +- `-clean`: Delete all gdev-prefixed resources + +## Prefix + +All resources are automatically prefixed with "gdev-" to avoid conflicts with production data. diff --git a/devenv/scopes/scopes-config.yaml b/devenv/scopes/scopes-config.yaml new file mode 100644 index 00000000000..46592742bbd --- /dev/null +++ b/devenv/scopes/scopes-config.yaml @@ -0,0 +1,84 @@ +scopes: + app1: + title: Application 1 + filters: + - key: app + operator: equals + value: app1 + + app2: + title: Application 2 + filters: + - key: app + operator: equals + value: app2 + + cluster1: + title: Cluster 1 + filters: + - key: cluster + operator: equals + value: cluster1 + +tree: + gdev-scopes: + title: gdev-scopes + nodeType: container + children: + production: + title: Production + nodeType: container + children: + app1-prod: + title: Application 1 + nodeType: leaf + linkId: app1 + linkType: scope + app2-prod: + title: Application 2 + nodeType: leaf + linkId: app2 + linkType: scope + test-cases: + title: Test cases + nodeType: container + disableMultiSelect: true + children: + test-case-1: + title: Test case 1 + nodeType: leaf + linkId: test-case-1 + linkType: scope + test-case-2: + title: Test case 2 + nodeType: leaf + linkId: test-case-2 + linkType: scope + + clusters: + title: Clusters + nodeType: container + linkId: cluster1 + linkType: scope + children: + cluster1-node: + title: Cluster 1 + nodeType: leaf + linkId: cluster1 + linkType: scope + +navigations: + # Example: Link to a dashboard + app1-nav: + url: /d/86Js1xRmk + scope: app1 + + # Example: Link to a dashboard with full URL (already has /d/) + app2-nav: + url: /d/GlAqcPgmz + scope: app2 + + # Example: Custom URL path + custom-nav: + url: /explore + scope: app1 diff --git a/devenv/scopes/scopes.go b/devenv/scopes/scopes.go new file mode 100644 index 00000000000..0480ee221e7 --- /dev/null +++ b/devenv/scopes/scopes.go @@ -0,0 +1,433 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + + "gopkg.in/yaml.v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1" +) + +const ( + prefix = "gdev" + apiVersion = "scope.grafana.app/v0alpha1" + defaultURL = "http://localhost:3000" + defaultUser = "admin" +) + +var ( + grafanaURL = flag.String("url", getEnv("GRAFANA_URL", defaultURL), "Grafana URL") + namespace = flag.String("namespace", getEnv("GRAFANA_NAMESPACE", "default"), "Namespace") + configFile = flag.String("config", "scopes-config.yaml", "Config file path") + user = flag.String("user", getEnv("GRAFANA_USER", defaultUser), "Grafana username") + password = flag.String("password", getEnv("GRAFANA_PASSWORD", "admin"), "Grafana password") + cleanupFlag = flag.Bool("clean", false, "Delete all gdev-prefixed resources") +) + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +type Config struct { + Scopes map[string]ScopeConfig `yaml:"scopes"` + Tree map[string]TreeNode `yaml:"tree"` + Navigations map[string]NavigationConfig `yaml:"navigations"` +} + +// ScopeConfig is used for YAML parsing - converts to v0alpha1.ScopeSpec +type ScopeConfig struct { + Title string `yaml:"title"` + Filters []ScopeFilterConfig `yaml:"filters"` +} + +// ScopeFilterConfig is used for YAML parsing - converts to v0alpha1.ScopeFilter +type ScopeFilterConfig struct { + Key string `yaml:"key"` + Value string `yaml:"value"` + Values []string `yaml:"values,omitempty"` + Operator string `yaml:"operator"` +} + +// TreeNode is used for YAML parsing - converts to v0alpha1.ScopeNodeSpec +type TreeNode struct { + Title string `yaml:"title"` + NodeType string `yaml:"nodeType"` + LinkID string `yaml:"linkId,omitempty"` + LinkType string `yaml:"linkType,omitempty"` + Children map[string]TreeNode `yaml:"children,omitempty"` +} + +type NavigationConfig struct { + URL string `yaml:"url"` // URL path (e.g., /d/abc123 or /explore) + Scope string `yaml:"scope"` +} + +// Helper function to convert ScopeFilterConfig to v0alpha1.ScopeFilter +func convertFilter(cfg ScopeFilterConfig) v0alpha1.ScopeFilter { + filter := v0alpha1.ScopeFilter{ + Key: cfg.Key, + Value: cfg.Value, + Values: cfg.Values, + Operator: v0alpha1.FilterOperator(cfg.Operator), + } + return filter +} + +// Helper function to convert ScopeConfig to v0alpha1.ScopeSpec +func convertScopeSpec(cfg ScopeConfig) v0alpha1.ScopeSpec { + filters := make([]v0alpha1.ScopeFilter, len(cfg.Filters)) + for i, f := range cfg.Filters { + filters[i] = convertFilter(f) + } + return v0alpha1.ScopeSpec{ + Title: cfg.Title, + Filters: filters, + } +} + +type Client struct { + baseURL string + namespace string + httpClient *http.Client + auth string +} + +func NewClient(baseURL, namespace, user, password string) *Client { + return &Client{ + baseURL: baseURL, + namespace: namespace, + httpClient: &http.Client{}, + auth: basicAuth(user, password), + } +} + +func basicAuth(username, password string) string { + return fmt.Sprintf("%s:%s", username, password) +} + +func (c *Client) makeRequest(method, endpoint string, body []byte) error { + url := fmt.Sprintf("%s/apis/%s/namespaces/%s%s", c.baseURL, apiVersion, c.namespace, endpoint) + + var req *http.Request + var err error + + if body != nil { + req, err = http.NewRequest(method, url, bytes.NewBuffer(body)) + } else { + req, err = http.NewRequest(method, url, nil) + } + + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1]) + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + bodyBytes, _ := io.ReadAll(resp.Body) + // For DELETE requests, 404 is acceptable (resource already deleted) + if resp.StatusCode == 404 { + return nil + } + return fmt.Errorf("API request failed: HTTP %d - %s", resp.StatusCode, string(bodyBytes)) + } + + return nil +} + +func (c *Client) createScope(name string, cfg ScopeConfig) error { + prefixedName := prefix + "-" + name + + spec := convertScopeSpec(cfg) + + resource := v0alpha1.Scope{ + TypeMeta: metav1.TypeMeta{ + APIVersion: apiVersion, + Kind: "Scope", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: prefixedName, + }, + Spec: spec, + } + + body, err := json.Marshal(resource) + if err != nil { + return fmt.Errorf("failed to marshal scope: %w", err) + } + + fmt.Printf("✓ Creating scope: %s\n", prefixedName) + return c.makeRequest("POST", "/scopes", body) +} + +func (c *Client) createScopeNode(name string, node TreeNode, parentName string) error { + prefixedName := prefix + "-" + name + prefixedParent := "" + prefixedLinkID := "" + + if parentName != "" { + prefixedParent = prefix + "-" + parentName + } + + if node.LinkID != "" { + prefixedLinkID = prefix + "-" + node.LinkID + } + + nodeType := v0alpha1.NodeType(node.NodeType) + if nodeType == "" { + nodeType = v0alpha1.NodeTypeContainer + } + + linkType := v0alpha1.LinkType(node.LinkType) + if linkType == "" { + linkType = v0alpha1.LinkTypeScope + } + + spec := v0alpha1.ScopeNodeSpec{ + Title: node.Title, + NodeType: nodeType, + DisableMultiSelect: false, + } + + if prefixedParent != "" { + spec.ParentName = prefixedParent + } + + if prefixedLinkID != "" { + spec.LinkID = prefixedLinkID + spec.LinkType = linkType + } + + resource := v0alpha1.ScopeNode{ + TypeMeta: metav1.TypeMeta{ + APIVersion: apiVersion, + Kind: "ScopeNode", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: prefixedName, + }, + Spec: spec, + } + + body, err := json.Marshal(resource) + if err != nil { + return fmt.Errorf("failed to marshal scope node: %w", err) + } + + fmt.Printf("✓ Creating scope node: %s\n", prefixedName) + return c.makeRequest("POST", "/scopenodes", body) +} + +func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error { + prefixedName := prefix + "-" + name + prefixedScope := prefix + "-" + nav.Scope + + if nav.URL == "" { + return fmt.Errorf("navigation %s must have 'url' specified", name) + } + + spec := v0alpha1.ScopeNavigationSpec{ + URL: nav.URL, + Scope: prefixedScope, + } + + resource := v0alpha1.ScopeNavigation{ + TypeMeta: metav1.TypeMeta{ + APIVersion: apiVersion, + Kind: "ScopeNavigation", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: prefixedName, + }, + Spec: spec, + } + + body, err := json.Marshal(resource) + if err != nil { + return fmt.Errorf("failed to marshal scope navigation: %w", err) + } + + fmt.Printf("✓ Creating scope navigation: %s\n", prefixedName) + return c.makeRequest("POST", "/scopenavigations", body) +} + +func (c *Client) createTreeNodes(children map[string]TreeNode, parentName string) error { + for name, node := range children { + // Build full node name by appending to parent name + // This makes it easy to see the tree path from the node name + fullNodeName := name + if parentName != "" { + fullNodeName = parentName + "-" + name + } + + // parentName here is the full parent name (already includes full path) + err := c.createScopeNode(fullNodeName, node, parentName) + if err != nil { + return err + } + + if len(node.Children) > 0 { + // Pass fullNodeName as parent for children (will be prefixed with "gdev-" in createScopeNode) + if err := c.createTreeNodes(node.Children, fullNodeName); err != nil { + return err + } + } + } + + return nil +} + +func (c *Client) deleteResources() { + fmt.Println("Deleting all gdev-prefixed resources...") + + // Delete scopes (silently handle errors if endpoints aren't available) + c.deleteResourceType("/scopes", "scope") + + // Delete scope nodes + c.deleteResourceType("/scopenodes", "scope node") + + // Delete scope navigations + c.deleteResourceType("/scopenavigations", "scope navigation") + + fmt.Println("✓ Cleanup complete") +} + +func (c *Client) deleteResourceType(endpoint, resourceType string) { + url := fmt.Sprintf("%s/apis/%s/namespaces/%s%s", c.baseURL, apiVersion, c.namespace, endpoint) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + // Silently skip if we can't create request + return + } + + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1]) + + resp, err := c.httpClient.Do(req) + if err != nil { + // Silently skip if endpoint isn't available + return + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + // Silently skip if endpoint returns error (might not be available) + return + } + + var listResponse struct { + Items []struct { + Metadata struct { + Name string `json:"name"` + } `json:"metadata"` + } `json:"items"` + } + + bodyBytes, _ := io.ReadAll(resp.Body) + if err := json.Unmarshal(bodyBytes, &listResponse); err != nil { + // Silently skip if we can't decode response + return + } + + if len(listResponse.Items) == 0 { + return + } + + deletedCount := 0 + for _, item := range listResponse.Items { + if strings.HasPrefix(item.Metadata.Name, prefix+"-") { + fmt.Printf(" Deleting %s: %s\n", resourceType, item.Metadata.Name) + deleteURL := fmt.Sprintf("%s/%s", endpoint, item.Metadata.Name) + if err := c.makeRequest("DELETE", deleteURL, nil); err != nil { + // Silently skip deletion errors + } else { + deletedCount++ + } + } + } +} + +func main() { + flag.Parse() + + client := NewClient(*grafanaURL, *namespace, *user, *password) + + if *cleanupFlag { + // Cleanup should be silent if endpoints aren't available + client.deleteResources() + return + } + + configData, err := os.ReadFile(*configFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading config file: %v\n", err) + os.Exit(1) + } + + var config Config + if err := yaml.Unmarshal(configData, &config); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n", err) + os.Exit(1) + } + + fmt.Printf("Loading configuration from: %s\n", *configFile) + fmt.Printf("Grafana URL: %s\n", *grafanaURL) + fmt.Printf("Namespace: %s\n", *namespace) + fmt.Printf("Prefix: %s\n\n", prefix) + + // Create scopes + fmt.Println("Creating scopes...") + for name, scope := range config.Scopes { + if err := client.createScope(name, scope); err != nil { + fmt.Fprintf(os.Stderr, "Error creating scope %s: %v\n", name, err) + os.Exit(1) + } + } + fmt.Println() + + // Create scope nodes (tree structure) + if len(config.Tree) > 0 { + fmt.Println("Creating scope nodes...") + if err := client.createTreeNodes(config.Tree, ""); err != nil { + fmt.Fprintf(os.Stderr, "Error creating scope nodes: %v\n", err) + os.Exit(1) + } + fmt.Println() + } + + // Create scope navigations + if len(config.Navigations) > 0 { + fmt.Println("Creating scope navigations...") + for name, nav := range config.Navigations { + if err := client.createScopeNavigation(name, nav); err != nil { + fmt.Fprintf(os.Stderr, "Error creating scope navigation %s: %v\n", name, err) + os.Exit(1) + } + } + fmt.Println() + } + + fmt.Println("✓ All resources created successfully!") +} diff --git a/devenv/setup.sh b/devenv/setup.sh index 00eb50b5257..d3774ca19ec 100755 --- a/devenv/setup.sh +++ b/devenv/setup.sh @@ -19,6 +19,13 @@ bulkFolders() { ln -s -f ../../../devenv/bulk-folders/bulk-folders.yaml ../conf/provisioning/dashboards/bulk-folders.yaml } +scopes() { + echo -e "\xE2\x9C\x94 Setting up scopes, scope nodes, and scope navigations" + cd scopes + go run scopes.go + cd .. +} + requiresJsonnet() { if ! type "jsonnet" > /dev/null; then echo "you need you install jsonnet to run this script" @@ -49,6 +56,12 @@ undev() { rm -rf bulk-folders/Bulk\ Folder* echo -e " \xE2\x9C\x94 Reverting bulk-folders provisioning" + # Removing scopes, scope nodes, and scope navigations + cd scopes + go run scopes.go -clean + cd .. + echo -e " \xE2\x9C\x94 Deleting scopes, scope nodes, and scope navigations" + # Removing the symlinks rm -f ../conf/provisioning/dashboards/custom.yaml rm -f ../conf/provisioning/dashboards/bulk-folders.yaml @@ -63,6 +76,7 @@ usage() { echo " bulk-dashboards - provision 400 dashboards" echo " bulk-folders [folders] [dashboards] - provision many folders with dashboards" echo " bulk-folders - provision 200 folders with 3 dashboards in each" + echo " scopes - provision scopes, scope nodes, and scope navigations" echo " no args - provision core datasources and dev dashboards" echo " undev - removes any provisioning done by the setup.sh" } @@ -80,6 +94,8 @@ main() { bulkDashboard elif [[ $cmd == "bulk-folders" ]]; then bulkFolders "$arg1" + elif [[ $cmd == "scopes" ]]; then + scopes elif [[ $cmd == "undev" ]]; then undev else diff --git a/docs/sources/administration/plugin-management/plugin-frontend-sandbox.md b/docs/sources/administration/plugin-management/plugin-frontend-sandbox.md index 19c5c6f72b3..1b2721d49a8 100644 --- a/docs/sources/administration/plugin-management/plugin-frontend-sandbox.md +++ b/docs/sources/administration/plugin-management/plugin-frontend-sandbox.md @@ -53,11 +53,7 @@ The following applies: ## Enable the Frontend Sandbox -The Frontend Sandbox feature is currently behind the `pluginsFrontendSandbox` feature flag. To enable it, you need to: - -1. Enable the feature flag in your Grafana configuration. For more information about enabling feature flags, refer to [Configure feature toggles](/docs/grafana//setup-grafana/configure-grafana/feature-toggles/). - -2. For self-hosted Grafana installations, add the plugin IDs you want to sandbox in the `security` section using the `enable_frontend_sandbox_for_plugins` configuration option. +For self-hosted Grafana installations, add the plugin IDs you want to sandbox in the `security` section using the `enable_frontend_sandbox_for_plugins` configuration option. For Grafana Cloud users, you can simply use the toggle switch in the plugin catalog page to enable or disable the sandbox for each plugin. By default, the sandbox is disabled for all plugins. diff --git a/docs/sources/administration/provisioning/index.md b/docs/sources/administration/provisioning/index.md index 51fd1f4c604..fadb936e5a0 100644 --- a/docs/sources/administration/provisioning/index.md +++ b/docs/sources/administration/provisioning/index.md @@ -375,6 +375,58 @@ providers: ``` When Grafana starts, it updates or creates all dashboards found in the configured path. +These files can define the dashboard using the dashboard JSON: + +```json +{ + "dashboard": { + "id": null, + "uid": "example-dashboard", + "title": "Production Overview", + "tags": ["production", "monitoring"], + "timezone": "browser", + "schemaVersion": 16, + "version": 0, + "refresh": "30s" + }, + "folderUid": "monitoring-folder", + "overwrite": true +} +``` + +Or using a Kubernetes format, for example `kubernetes-dashboard.json`: + +```json +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "dashboard-uid" + }, + "spec": { + "title": "Dashboard title", + "panels": [ + { + "gridPos": { + "h": 13, + "w": 24, + "x": 0, + "y": 0 + }, + "options": { + "content": "

Example panel

", + "mode": "html" + }, + "transparent": true, + "type": "text" + } + ] + } +} +``` + +You _must_ use the Kubernetes resource format to provision dashboards v2 / dynamic dashboards. + It later polls that path every `updateIntervalSeconds` for updates to the dashboard files and updates its database. {{< admonition type="note" >}} diff --git a/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md b/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md index 224ec27ea82..746d2a0084d 100644 --- a/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md +++ b/docs/sources/alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules.md @@ -70,6 +70,10 @@ If a rule does not explicitly specify a target data source for writing (for exam default_datasource_uid = my-uid ``` +{{< admonition type="note" >}} +Grafana Cloud: If you leave **Target data source** blank when creating a recording rule, Grafana writes the results to your managed Prometheus data source named `grafanacloud-prom` by default. This may be different from the default data source you use in dashboards. To write to a different backend, explicitly select a target data source. In self-managed Grafana, you can set the default fallback with `default_datasource_uid` in the `[recording_rules]` section of the configuration. +{{< /admonition >}} + If you previously configured recording rules using the `url` and `basic_auth_*` configuration options, these are no longer supported. You must either: - Set `default_datasource_uid` in the `[recording_rules]` section of the configuration file to point to the target data source diff --git a/docs/sources/alerting/alerting-rules/templates/reference.md b/docs/sources/alerting/alerting-rules/templates/reference.md index d638b8e1d3c..b80aed07766 100644 --- a/docs/sources/alerting/alerting-rules/templates/reference.md +++ b/docs/sources/alerting/alerting-rules/templates/reference.md @@ -141,6 +141,20 @@ Alternatively, you can use the `index()` function to retrieve the query value: {{ index $values "B" }} CPU usage for {{ index $labels "instance" }} over the last 5 minutes. ``` +{{< admonition type="note" >}} + +Variable names that start with a number (for example, `1B`) are not [valid identifiers in Go templates](https://go.dev/ref/spec#Identifiers). + +To access a value or label whose key starts with a number, use the `index` function: + +``` +{{ index $values "1B" }} CPU usage for {{ index $labels "1instance" }} over the last 5 minutes. +``` + +Using `{{ $values.1B.Value }}` is invalid and causes the template code to render as plain text. + +{{< /admonition >}} + #### $value The `$value` variable is a string containing the labels and values of all instant queries; threshold, reduce and math expressions, and classic conditions in the alert rule. diff --git a/docs/sources/alerting/best-practices/_index.md b/docs/sources/alerting/best-practices/_index.md index 41251a25994..4e08dd8ef37 100644 --- a/docs/sources/alerting/best-practices/_index.md +++ b/docs/sources/alerting/best-practices/_index.md @@ -47,4 +47,11 @@ Designing and configuring an alert management set up that works takes time. Here - Continually tune your alert rules to review effectiveness. Remove alert rules to avoid duplication or ineffective alerts. - Continually review your thresholds and evaluation rules. +**How should you configure recording rules?** + +- Use frequent evaluation intervals. It is recommended to set a frequent evaluation interval for recording rules. Long intervals, such as an hour, can cause the recorded metric to be stale and lead to misaligned alert rule evaluations, especially when combined with a long pending period. +- Understand query types. Grafana Alerting uses both **Instant** and **Range** queries. Instant queries fetch a single data point, while Range queries fetch a series of data points over time. When using a Range query in an alert condition, you must use a Reduce expression to aggregate the series into a single value. +- Align alert evaluation with recording frequency. The evaluation interval of an alert rule that depends on a recorded metric should be aligned with the recording rule's interval. If a recording rule runs every 3 minutes, the alert rule should also be evaluated at a similar frequency to ensure it acts on fresh data. +- Use `_over_time` functions for instant queries. Since all alert rules are ultimately executed as an instant query, you can use functions like `max_over_time(my_metric[1h])` as an instant query. This allows you to get an aggregated value over a period without using a range query and a reduce expression. + {{< /shared >}} diff --git a/docs/sources/alerting/configure-notifications/mute-timings.md b/docs/sources/alerting/configure-notifications/mute-timings.md index f9cb3bd5086..58636eb1965 100644 --- a/docs/sources/alerting/configure-notifications/mute-timings.md +++ b/docs/sources/alerting/configure-notifications/mute-timings.md @@ -46,7 +46,7 @@ A mute timing is a recurring interval that stops notifications for one or multip Use mute timings to temporarily pause notifications for a specific recurring period, such as a regular maintenance window or weekends. -The active time interval provide the opposite functionality, where alerts handled by a notification policy are suppressed unless the notification happens at a time that matches the time interval. Use active time intervals for periods where you want to reduce alert noise. +The active time interval provide the opposite functionality, where alerts handled by a notification policy are suppressed unless the notification happens at a time that matches the time interval. Use active time intervals for periods where you want to reduce alert noise. Mute timings take precedence over active time intervals when they overlap. {{< admonition type="note" >}} Mute timings and active time intervals are assigned to a [specific Alertmanager](ref:alertmanager-architecture) and only suppress notifications for alerts managed by that Alertmanager. diff --git a/docs/sources/alerting/set-up/performance-limitations/index.md b/docs/sources/alerting/set-up/performance-limitations/index.md index 82e8ccd41f1..b18f3b6ec68 100644 --- a/docs/sources/alerting/set-up/performance-limitations/index.md +++ b/docs/sources/alerting/set-up/performance-limitations/index.md @@ -68,7 +68,21 @@ You can change this behavior by disabling the `alertingSaveStateCompressed` feat You can also reduce database load by writing states periodically instead of after every evaluation. -To save state periodically: +There are two approaches for periodic state saving: + +#### Compressed periodic saves + +You can combine compressed alert state storage with periodic saves by enabling both `alertingSaveStateCompressed` and `alertingSaveStatePeriodic` feature toggles together. + +This approach groups all alert instances by rule UID and compresses them together for efficient storage. + +When both feature toggles are enabled, Grafana will save compressed alert states at the interval specified by `state_periodic_save_interval`. Note that in compressed mode, the `state_periodic_save_batch_size` setting is ignored as the system groups instances by rule UID rather than by batch size. + +#### Batch-based periodic saves + +Alternatively, you can use batch-based periodic saves without compression: + +This approach processes individual alert instances in batches of a specified size. 1. Enable the `alertingSaveStatePeriodic` feature toggle. 1. Disable the `alertingSaveStateCompressed` feature toggle. @@ -77,7 +91,7 @@ By default, it saves the states every 5 minutes to the database and on each shut can also be configured using the `state_periodic_save_interval` configuration flag. During this process, Grafana deletes all existing alert instances from the database and then writes the entire current set of instances back in batches in a single transaction. Configure the size of each batch using the `state_periodic_save_batch_size` configuration option. -#### Jitter for periodic saves +##### Jitter for batch-based periodic saves To further distribute database load, you can enable jitter for periodic state saves by setting `state_periodic_save_jitter_enabled = true`. When jitter is enabled, instead of saving all batches simultaneously, Grafana spreads the batch writes across a calculated time window of 85% of the save interval. diff --git a/docs/sources/datasources/aws-cloudwatch/query-editor/index.md b/docs/sources/datasources/aws-cloudwatch/query-editor/index.md index 02b229e50f9..9bc7ab64047 100644 --- a/docs/sources/datasources/aws-cloudwatch/query-editor/index.md +++ b/docs/sources/datasources/aws-cloudwatch/query-editor/index.md @@ -250,6 +250,19 @@ You can query CloudWatch Logs using three supported query language options: 1. Select a region. 1. Select **CloudWatch Logs** from the query type drop-down. +1. Select the Logs Mode depending on whether you would like to query CloudWatch Logs Insights or Log Anomalies + +**Log Anomalies** + +[Anomaly detection](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/LogsAnomalyDetection.html) uses machine-learning and pattern recognition to establish baselines of typical log content. +The Log Anomalies query editor fetches the list of anomalies detected in your CloudWatch service. In order to query log anomalies in the editor, a log anomaly detector must be created in the AWS CloudWatch console first. +The log trend cell shows the number of occurrences of the pattern over the selected query time range. +The table shows 50 log anomalies at a time. If you would like to narrow down the list, you can filter anomalies by their ARN and suppressed state. + +In addition to this, you can use the Logs Insights QL editor and the `anomaly` command together with the `patterns` command to define and display log anomalies in real time. See the [CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/LogsAnomalyDetection-Insights.html) documentation for more info. + +**Logs Insights** + 1. Select the query language you would like to use in the **Query Language** drop-down. 1. Click **Select log groups** and choose up to 20 log groups to query. 1. Use the main input area to write your logs query. Amazon CloudWatch only supports a subset of OpenSearch SQL and PPL commands. To find out more about the syntax supported, consult [Amazon CloudWatch Logs documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData_Languages.html) @@ -258,7 +271,7 @@ You can query CloudWatch Logs using three supported query language options: You must specify the region and log groups when querying with **Logs Insights QL** and **OpenSearch PPL**. **OpenSearch SQL** doesn't require log group selection. However, selecting log groups simplifies query writing by populating syntax suggestions with discovered log group fields. {{< /admonition >}} -Click **CloudWatch Logs Insights** to interactively view, search, and analyze your log data in the CloudWatch Logs Insights console. If you're not logged in to the CloudWatch console, the link forwards you to the login page. +Click **View in CloudWatch console** to interactively view, search, and analyze your log data in the CloudWatch Logs Insights console. If you're not logged in to the CloudWatch console, the link forwards you to the login page. ### Query Log groups with OpenSearch SQL diff --git a/docs/sources/developers/http_api/query_and_resource_caching.md b/docs/sources/developers/http_api/query_and_resource_caching.md index 83da5e81d1d..1102f593a72 100644 --- a/docs/sources/developers/http_api/query_and_resource_caching.md +++ b/docs/sources/developers/http_api/query_and_resource_caching.md @@ -17,7 +17,6 @@ keywords: labels: products: - enterprise - - oss title: Query and Resource Caching HTTP API --- diff --git a/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md b/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md index 33489050eed..e0c0d0fe51a 100644 --- a/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md +++ b/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md @@ -18,7 +18,7 @@ weight: 300 # Manage resources with Grafana CLI {{< admonition type="note" >}} -`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support. +`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support. You can find a full list of supported commands [in this page](https://grafana.github.io/grafanactl/reference/cli/grafanactl/). {{< /admonition >}} ## Migrate resources between environments diff --git a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md index 5a1a32339ee..5b1b1809463 100644 --- a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md +++ b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md @@ -10,6 +10,12 @@ labels: - enterprise - oss - cloud +refs: + roles-and-permissions: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/roles-and-permissions/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/account-management/authentication-and-permissions/cloud-roles/ title: Git Sync weight: 100 --- @@ -84,6 +90,11 @@ Refer to [Requirements](https://grafana.com/docs/grafana//obser - You can only authenticate in GitHub using your Personal Access Token token. +**Permission management** + +- You cannot modify the permissions of a provisioned folder after you've synced it. +- Default permissions are: Admin = Admin, Editor = Editor, and Viewer = Viewer. Refer to [Roles and permissions](ref:roles-and-permissions) for more information. + **Compatibility** - Support for native Git, Git app, and other providers, such as GitLab or Bitbucket, is on the roadmap. diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index ea83a3bb553..01b8472f681 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -657,12 +657,16 @@ If you want to track Grafana usage via Azure Application Insights, then specify Optionally, use this option to override the default endpoint address for Application Insights data collecting. For details, refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/app/custom-endpoints?tabs=js). -
+#### `application_insights_auto_route_tracking` + +Optionally, use this to configure `enableAutoRouteTracking` in Azure Application Insights. Defaults to `true`. For more details, refer to the [Azure documentation](https://learn.microsoft.com/en-us/azure/azure-monitor/app/application-insights-faq#is-there-a-way-to-see-fewer-events-per-transaction-when-i-use-the-application-insights-javascript-sdk) #### `feedback_links_enabled` Set to `false` to remove all feedback links from the UI. Default is `true`. +
+ ### `[security]` #### `disable_initial_admin_creation` @@ -1969,6 +1973,12 @@ If a rule frequency is lower than this value, then this value is enforced.
+#### `rule_version_record_limit` + +Defines the limits for how many alert rule versions are stored in the database per alert rule. + +The default `0` value means there's no limit. + ### `[unified_alerting.screenshots]` For more information about screenshots, refer to [Images in notifications](../../alerting/configure-notifications/template-notifications/images-in-notifications/). @@ -2616,6 +2626,15 @@ These will be installed before starting Grafana. Useful when used with provision This option disables all preinstalled plugins. The default is `false`. To disable a specific plugin from being preinstalled, use the `disable_plugins` option. +#### `preinstall_auto_update` + +Enable automatic updates for preinstalled plugins on start-up. +When enabled, preinstalled plugins without a pinned version are automatically updated to the latest version when Grafana starts. + +The default is `true`. + +To prevent automatic updates for specific plugins, pin them to a specific version using the format `plugin_id@version` in the `preinstall` setting. +
### `[live]` diff --git a/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md b/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md index 82eec98249a..601a258764f 100644 --- a/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md +++ b/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md @@ -555,10 +555,6 @@ A space-separated list of memcached servers. Example: `memcached-server-1:11211 The default is `"localhost:11211"`. -{{< admonition type="note" >}} -The following memcached configuration requires the `tlsMemcached` feature toggle. -{{< /admonition >}} - ### tls_enabled Enables TLS authentication for memcached. Defaults to `false`. diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index b9d830495c5..0b802145d87 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -27,20 +27,17 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | | | `publicDashboardsScene` | Enables public dashboard rendering using scenes | Yes | | `featureHighlights` | Highlight Grafana Enterprise features | | -| `correlations` | Correlations page | Yes | | `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes | | `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes | | `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes | | `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes | | `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes | | `unifiedRequestLog` | Writes error logs to the request logger | Yes | -| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes | | `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | Yes | | `transformationsRedesign` | Enables the transformations redesign | Yes | | `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes | | `dashgpt` | Enable AI powered features in dashboards | Yes | -| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | | `formatString` | Enable format string transformer | Yes | | `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards | Yes | | `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes | @@ -54,7 +51,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate various on-prem resources to your Grafana Cloud stack. | Yes | | `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes | | `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | Yes | -| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes | | `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes | | `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | | | `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | Yes | @@ -67,7 +63,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes | | `pluginsSriChecks` | Enables SRI checks for plugin assets | | | `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | | -| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes | | `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes | | `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes | | `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes | @@ -81,8 +76,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `grafanaAssistantInProfilesDrilldown` | Enables integration with Grafana Assistant in Profiles Drilldown | Yes | | `sharingDashboardImage` | Enables image sharing functionality for dashboards | Yes | | `tabularNumbers` | Use fixed-width numbers globally in the UI | | -| `dashboardDsAdHocFiltering` | Enables adhoc filtering support for the dashboard datasource | Yes | -| `adhocFiltersInTooltips` | Enable adhoc filter buttons in visualization tooltips | Yes | | `tempoSearchBackendMigration` | Run search queries through the tempo backend | | ## Public preview feature toggles @@ -113,6 +106,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `logsPanelControls` | Enables a control component for the logs panel in Explore | | `interactiveLearning` | Enables the interactive learning app | | `azureResourcePickerUpdates` | Enables the updated Azure Monitor resource picker | +| `newVizSuggestions` | Enable new visualization suggestions | | `preventPanelChromeOverflow` | Restrict PanelChrome contents with overflow: hidden; | ## Development feature toggles diff --git a/docs/sources/setup-grafana/configure-grafana/proxy/index.md b/docs/sources/setup-grafana/configure-grafana/proxy/index.md index 7f17909ad5d..c448b252cce 100644 --- a/docs/sources/setup-grafana/configure-grafana/proxy/index.md +++ b/docs/sources/setup-grafana/configure-grafana/proxy/index.md @@ -15,35 +15,35 @@ weight: 1110 # Configure a data source SOCKS5 connection proxy -Grafana provides support for proxying data source connections through a Secure Socks5 Tunnel. This enables you to securely connect to data sources hosted in a different network than Grafana. +Grafana provides support for proxying data source connections through a secure SOCKS5 proxy. This enables you to securely connect to data sources hosted in a different network than Grafana. -To make use of this functionality, you need to deploy a socks5 proxy server that supports TLS on a machine exposed to the public internet within the same network as your data source. From there, Grafana establishes a mutually trusted connection from Grafana to the Proxy. Then the Proxy can proxy the Grafana connection to your private server without exposing your data sources to the public internet. +To make use of this functionality, you need to deploy a SOCKS5 proxy server that supports TLS on a machine accessible from where your Grafana instance is running, and within the same network as your data source. From there, Grafana establishes a mutually trusted connection from Grafana to the proxy. Then the SOCKS5 proxy can proxy the Grafana data source connection to your private data source instance without exposing your data source to the public internet. ## Known limitations -- You can configure only one socks5 proxy per Grafana instance +- You can configure only one SOCKS5 proxy per Grafana instance. This is because the SOCKS5 proxy address and TLS configuration is set at the Grafana instance level. - All built-in core data sources are compatible, but not all external data sources are. For a list of supported data sources, refer to [private data source connect](/docs/grafana-cloud/data-configuration/configure-private-datasource-connect/#known-limitations). ## Before you begin -To complete this task, you must first deploy a socks proxy server that supports TLS, is publicly accessible, and is hosted within the same network as the data source. +To complete this task, you must first have a SOCKS5 proxy server running, using host certificates signed by a Certificate Authority (CA) that you can get the public certificate for. You must also have a TLS client key pair for Grafana, with the client certificate signed by the same CA used to sign the proxy server certificate. ## Steps 1. For Grafana to send data source connections to the socks5 server, use the following table to configure the `secure_socks_datasource_proxy` section of the `config.ini`: - | Key | Description | Example | - | ---------------- | ------------------------------------------ | ------------------------------- | - | `enabled` | Enable this feature in Grafana | true | - | `root_ca_cert` | The file path of the root ca cert | /etc/ca.crt | - | `client_key` | The file path of the client private key | /etc/client.key | - | `client_cert` | The file path of the client public key | /etc/client.crt | - | `server_name` | The domain name of the proxy, used for SNI | proxy.grafana.svc.cluster.local | - | `proxy_address` | The address of the proxy | localhost:9090 | - | `allow_insecure` | Disable TLS in the socks proxy | false | + | Key | Description | Example | + | ---------------- | --------------------------------------------------------------------------- | ------------------------------- | + | `enabled` | Enable this feature in Grafana | true | + | `root_ca_cert` | The file path of the root ca cert used to sign the proxy server certificate | /etc/ca.crt | + | `client_key` | The file path of the client private key | /etc/client.key | + | `client_cert` | The file path of the client public key | /etc/client.crt | + | `server_name` | The domain name of the proxy, used for SNI | proxy.grafana.svc.cluster.local | + | `proxy_address` | The address of the proxy | localhost:9090 | + | `allow_insecure` | Disable TLS in the socks proxy | false | 1. Set up a data source and configure it to send data source connections through the proxy. To configure your data sources to send connections through the proxy, `enableSecureSocksProxy=true` must be specified in the data source json. You can do this in the [API](../../../developers/http_api/data_source/) or use [file based provisioning](../../../administration/provisioning/#data-sources). - Additionally, you can set the socks5 username and password by adding `secureSocksProxyUsername` in the data source json and `secureSocksProxyPassword` in the secure data source json. + Additionally, if using SOCKS5 authentication, you can set the SOCKS5 username and password by adding `secureSocksProxyUsername` in the data source's `jsonData` field and `secureSocksProxyPassword` in the data source's `secureJsonData` field. diff --git a/docs/sources/tutorials/run-grafana-behind-a-proxy/index.md b/docs/sources/tutorials/run-grafana-behind-a-proxy/index.md index 6b791739cbb..b1ea811531a 100644 --- a/docs/sources/tutorials/run-grafana-behind-a-proxy/index.md +++ b/docs/sources/tutorials/run-grafana-behind-a-proxy/index.md @@ -266,7 +266,7 @@ http: You only need this if you don't handle the sub path serving via your reverse proxy configuration. {{< /admonition >}} -If you don't want or can't use the reverse proxy to handle serving Grafana from a _sub path_, you can set the configuration variable `server_from_sub_path` to `true`. +If you don't want or can't use the reverse proxy to handle serving Grafana from a _sub path_, you can set the configuration variable `serve_from_sub_path` to `true`. 1. Include the sub path at the end of the `root_url`. 1. Set `serve_from_sub_path` to `true`: diff --git a/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md b/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md index dc4b41ce1fd..789d6617c18 100644 --- a/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md @@ -249,6 +249,7 @@ guaranteed because plugin developers can override this functionality. The follow - Dynatrace - Graphite - Google Sheets +- Tempo ### Unconfirmed diff --git a/docs/sources/whatsnew/_index.md b/docs/sources/whatsnew/_index.md index bb401ea136d..6779856cdeb 100644 --- a/docs/sources/whatsnew/_index.md +++ b/docs/sources/whatsnew/_index.md @@ -170,7 +170,6 @@ aliases: description: Learn about new and updated features in Grafana. labels: products: - - cloud - enterprise - oss menuTitle: What's new @@ -180,7 +179,9 @@ weight: 1 # What's new in Grafana -For release highlights, deprecations, and breaking changes in Grafana releases, refer to these "What's new" pages for each version. +For release highlights, deprecations, and breaking changes in self-managed Grafana releases, refer to these "What's new" pages for each version. + +For information on new Grafana Cloud highlights, refer to [What's new from Grafana Labs](https://grafana.com/whats-new). {{< admonition type="note" >}} For Grafana versions prior to v9.2, additional information might also be available in the archived release notes. To access archived release notes, use the documentation for the minor version you want to see. diff --git a/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts b/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts index 4e008fc3b5f..e47fb1b26f9 100644 --- a/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts +++ b/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts @@ -1,16 +1,9 @@ -import { Page, Locator } from '@playwright/test'; - import { test, expect } from '@grafana/plugin-e2e'; import testDashboard from '../dashboards/AdHocFilterTest.json'; +import { getCell } from '../panels-suite/table-utils'; -// Helper function to get a specific cell in a table -const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) => - loc - .getByRole('row') - .nth(rowIdx) - .getByRole(rowIdx === 0 ? 'columnheader' : 'gridcell') - .nth(colIdx); +const fixture = require('../fixtures/prometheus-response.json'); test.describe( 'Dashboard with Table powered by Prometheus data source', @@ -46,80 +39,90 @@ test.describe( gotoDashboardPage, selectors, }) => { - // Handle query and query_range API calls + // Handle query and query_range API calls. Ideally, this would instead be directly tested against gdev-prometheus. await page.route(/\/api\/ds\/query/, async (route) => { - const fixture = require('../fixtures/prometheus-response.json'); - // during the test, we select the "inner_eval" slice to filter; this simulates the behavior - // of prometheus applying that filter and removing dataframes from the response. - if (route.request().postData()?.includes('{slice=\\\"inner_eval\\\"}')) { - delete fixture.results.A.frames[1]; + const response = JSON.parse(JSON.stringify(fixture)); + + // This simulates the behavior of prometheus applying a filter and removing dataframes from the response where + // the label matches the selected filter. We check for either the slice being applied inline into the prometheus + // query or the adhoc filter being present in the request body of prometheus applying that filter and removing + // dataframes from the response. + const postData = route.request().postData(); + const match = + postData?.match(/{slice=\\\"([\w_]+)\\\"}/) ?? + postData?.match(/"adhocFilters":\[{"key":"slice","operator":"equals","value":"([\w_]+)"}\]/); + if (match) { + response.results.A.frames = response.results.A.frames.filter((frame) => + frame.schema.fields.every((field) => !field.labels || field.labels.slice === match[1]) + ); } await route.fulfill({ status: 200, contentType: 'application/json', - body: JSON.stringify(fixture), + body: JSON.stringify(response), }); }); const dashboardPage = await gotoDashboardPage({ uid: dashboardUID }); - const panel = dashboardPage.getByGrafanaSelector( + let panel = dashboardPage.getByGrafanaSelector( selectors.components.Panels.Panel.title('Table powered by Prometheus') ); - await expect(panel).toBeVisible(); + await expect(panel, 'panel is rendered').toBeVisible(); // Wait for the table to load completely - await expect(panel.locator('.rdg')).toBeVisible(); + const table = panel.locator('.rdg'); + await expect(table, 'table is rendered').toBeVisible(); - // Get the first data cell in the third column (row 1, column 2) - const labelValueCell = await getCell(panel, 1, 1); - await expect(labelValueCell).toBeVisible(); + const firstValue = (await getCell(table, 1, 1).textContent())!; + const secondValue = (await getCell(table, 2, 1).textContent())!; + expect(firstValue, `first cell is "${firstValue}"`).toBeTruthy(); + expect(secondValue, `second cell is "${secondValue}"`).toBeTruthy(); + expect(firstValue, 'first and second cell values are different').not.toBe(secondValue); - // Get the cell value before clicking the filter button - const labelValue = await labelValueCell.textContent(); - expect(labelValue).toBeTruthy(); + async function performTest(labelValue: string) { + // Confirm both cells are rendered before we proceed + const otherValue = labelValue === firstValue ? secondValue : firstValue; + await expect(table.getByText(labelValue), `"${labelValue}" is rendered`).toContainText(labelValue); + await expect(table.getByText(otherValue), `"${otherValue}" is rendered`).toContainText(otherValue); - const otherValueCell = await getCell(panel, 2, 1); - const otherValueLabel = await otherValueCell.textContent(); - expect(otherValueLabel).toBeTruthy(); - expect(otherValueLabel).not.toBe(labelValue); + // click the "Filter for value" button on the cell with the specified labelValue + await table.getByText(labelValue).hover(); + table.getByText(labelValue).getByRole('button', { name: 'Filter for value' }).click(); - // Hover over the first cell to trigger the appearance of filter actions - await labelValueCell.hover(); + // Look for submenu items that contain the filtered value + // The adhoc filter should appear as a filter chip or within the variable controls + const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem); + await expect(submenuItems.filter({ hasText: labelValue }), `submenu contains "${labelValue}"`).toBeVisible(); + await expect( + submenuItems.filter({ hasText: otherValue }), + `submenu does not contain "${otherValue}"` + ).toBeHidden(); - // Check if the "Filter for value" button appears on hover - const filterForValueButton = labelValueCell.getByRole('button', { name: 'Filter for value' }); - await expect(filterForValueButton).toBeVisible(); + // The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"] + const currentUrl = page.url(); + const urlParams = new URLSearchParams(new URL(currentUrl).search); + const promAdHocParam = urlParams.get('var-PromAdHoc'); + expect(promAdHocParam, `url contains "${labelValue}"`).toContain(labelValue); + expect(promAdHocParam, `url does not contain "${otherValue}"`).not.toContain(otherValue); - // Click on the "Filter for value" button - await filterForValueButton.click(); + // finally, let's check that the table was updated and that the value was filtered out when the query was re-run + await expect(table.getByText(labelValue), `"${labelValue}" is still visible`).toHaveText(labelValue); + await expect(table.getByText(otherValue), `"${otherValue}" is filtered out`).toBeHidden(); - // Check if the adhoc filter appears in the dashboard submenu - const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem); - await expect(submenuItems.first()).toBeVisible(); + // Remove the adhoc filter by clicking the submenu item again + const filterChip = submenuItems.filter({ hasText: labelValue }); + await filterChip.getByLabel(/Remove filter with key/).click(); + await page.click('body', { position: { x: 0, y: 0 } }); // click outside to close the open menu from ad-hoc filters - // Look for submenu items that contain the filtered value - // The adhoc filter should appear as a filter chip or within the variable controls - const hasFilterValue = await submenuItems.filter({ hasText: labelValue! }).count(); - expect(hasFilterValue).toBeGreaterThan(0); + // the "first" and "second" cells locators don't work here for some reason. + await expect(table.getByText(labelValue), `"${labelValue}" is still rendered`).toContainText(labelValue); + await expect(table.getByText(otherValue), `"${otherValue}" is rendered again`).toContainText(otherValue); + } - const hasOtherValue = await submenuItems.filter({ hasText: otherValueLabel! }).count(); - expect(hasOtherValue).toBe(0); - - // Check if the URL contains the var-PromAdHoc parameter with the filtered value - const currentUrl = page.url(); - expect(currentUrl).toContain('var-PromAdHoc'); - - // The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"] - const urlParams = new URLSearchParams(new URL(currentUrl).search); - const promAdHocParam = urlParams.get('var-PromAdHoc'); - expect(promAdHocParam).toBeTruthy(); - expect(promAdHocParam).toContain(labelValue!); - expect(promAdHocParam).not.toContain(otherValueLabel!); - - // finally, let's check that the table was updated and that the value was filtered out when the query was re-run - await expect(otherValueCell).toBeHidden(); + await performTest(firstValue); + await performTest(secondValue); }); } ); diff --git a/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts b/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts index 271e2f42b1f..2d56cd14512 100644 --- a/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts @@ -1,4 +1,4 @@ -import { addDays, addHours, differenceInCalendarDays, differenceInMinutes, isBefore, parseISO, toDate } from 'date-fns'; +import { differenceInMinutes, parseISO, toDate } from 'date-fns'; import { Page } from 'playwright-core'; import { test, expect, DashboardPage, E2ESelectorGroups } from '@grafana/plugin-e2e'; @@ -17,7 +17,7 @@ test.describe( tag: ['@dashboards'], }, () => { - test.fixme('Tests dashboard time zone scenarios', async ({ page, gotoDashboardPage, selectors }) => { + test('Tests dashboard time zone scenarios', async ({ page, gotoDashboardPage, selectors }) => { const dashboardPage = await gotoDashboardPage({ uid: TIMEZONE_DASHBOARD_UID }); const fromTimeZone = 'UTC'; @@ -106,12 +106,18 @@ test.describe( zone: 'Browser', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + const relativeTimeRow = dashboardPage + .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) + .locator('[role="row"]') + .filter({ hasText: '00:00:00' }) + .first(); + const timezoneRow = dashboardPage + .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone')) + .locator('[role="row"]') + .filter({ hasText: '00:00:00' }) + .first(); + + await expect(relativeTimeRow).toBeVisible(); // Today so far, still in Browser timezone await setTimeRange(page, dashboardPage, selectors, { @@ -119,19 +125,8 @@ test.describe( to: 'now', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); - - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); + await expect(timezoneRow).toBeVisible(); // Test UTC timezone await setTimeRange(page, dashboardPage, selectors, { @@ -140,12 +135,7 @@ test.describe( zone: 'Coordinated Universal Time', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); // Today so far, still in UTC timezone await setTimeRange(page, dashboardPage, selectors, { @@ -153,19 +143,8 @@ test.describe( to: 'now', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); - - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); + await expect(timezoneRow).toBeVisible(); // Test Tokyo timezone await setTimeRange(page, dashboardPage, selectors, { @@ -174,12 +153,7 @@ test.describe( zone: 'Asia/Tokyo', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); // Today so far, still in Tokyo timezone await setTimeRange(page, dashboardPage, selectors, { @@ -187,19 +161,8 @@ test.describe( to: 'now', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); - - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); + await expect(timezoneRow).toBeVisible(); // Test LA timezone await setTimeRange(page, dashboardPage, selectors, { @@ -208,12 +171,7 @@ test.describe( zone: 'America/Los Angeles', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); // Today so far, still in LA timezone await setTimeRange(page, dashboardPage, selectors, { @@ -221,19 +179,8 @@ test.describe( to: 'now', }); - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); - - await expect( - dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone')) - .locator('[role="row"]') - .filter({ hasText: '00:00:00' }) - ).toBeVisible(); + await expect(relativeTimeRow).toBeVisible(); + await expect(timezoneRow).toBeVisible(); }); } ); @@ -266,10 +213,7 @@ const isTimeCorrect = (inUtc: string, inTz: string, offset: number): boolean => } const utcDate = toDate(parseISO(inUtc)); - const utcDateWithOffset = addHours(toDate(parseISO(inUtc)), offset); - const dayDifference = differenceInCalendarDays(utcDate, utcDateWithOffset); // if the utcDate +/- offset is the day before/after then we need to adjust reference - const dayOffset = isBefore(utcDateWithOffset, utcDate) ? dayDifference * -1 : dayDifference; - const tzDate = addDays(toDate(parseISO(inTz)), dayOffset); // adjust tzDate with any dayOffset + const tzDate = toDate(parseISO(inTz)); const diff = Math.abs(differenceInMinutes(utcDate, tzDate)); // use Math.abs if tzDate is in future return diff <= Math.abs(offset * 60); diff --git a/e2e-playwright/panels-suite/frontend-sandbox-panel.spec.ts b/e2e-playwright/panels-suite/frontend-sandbox-panel.spec.ts index 4cdb7afb081..b9100201f02 100644 --- a/e2e-playwright/panels-suite/frontend-sandbox-panel.spec.ts +++ b/e2e-playwright/panels-suite/frontend-sandbox-panel.spec.ts @@ -22,133 +22,64 @@ test.describe( tag: ['@panels'], }, () => { - test.describe('Sandbox disabled', () => { - test.beforeEach(async ({ page }) => { - await page.addInitScript(() => { - window.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); - }); - await page.reload(); + test('Does not add iframes to body', async ({ page, gotoDashboardPage }) => { + await gotoDashboardPage({ + uid: DASHBOARD_ID, }); - test('Add iframes to body', async ({ page, gotoDashboardPage }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - }); + // this button adds 3 iframes to the body + await page.locator('[data-testid="button-create-iframes"]').click(); - // this button adds iframes to the body - await page.locator('[data-testid="button-create-iframes"]').click(); + const iframeIds = [ + 'createElementIframe', + 'innerHTMLIframe', + 'appendIframe', + 'prependIframe', + 'afterIframe', + 'beforeIframe', + 'outerHTMLIframe', + 'parseFromStringIframe', + 'insertBeforeIframe', + 'replaceChildIframe', + ]; - const iframeIds = [ - 'createElementIframe', - 'innerHTMLIframe', - 'appendIframe', - 'prependIframe', - 'afterIframe', - 'beforeIframe', - 'outerHTMLIframe', - 'parseFromStringIframe', - 'insertBeforeIframe', - 'replaceChildIframe', - ]; - - for (const id of iframeIds) { - await expect(page.locator(`#${id}`)).toBeVisible(); - } - }); - - test('Reaches out of panel div', async ({ page, gotoDashboardPage }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - }); - - // this button reaches out of the panel div and modifies the element dataset - await page.locator('[data-testid="button-reach-out"]').click(); - await expect(page.locator('[data-sandbox-test="true"]')).toBeVisible(); - }); - - test('Reaches out of the panel editor', async ({ gotoDashboardPage, page }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - queryParams: new URLSearchParams({ editPanel: '1' }), - }); - - const input = page.locator('[data-testid="panel-editor-custom-editor-input"]'); - await expect(input).toBeEnabled(); - await expect(input).toHaveValue(''); - - await input.fill('x'); - await expect(input).toHaveValue('x'); - await expect(page.locator('[data-sandbox-test="panel-editor"]')).toBeVisible(); - }); + for (const id of iframeIds) { + await expect(page.locator(`#${id}`)).toBeHidden(); + } }); - test.describe('Sandbox enabled', () => { - test.beforeEach(async ({ page }) => { - await page.addInitScript(() => { - window.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); - }); - await page.reload(); + test('Does not reaches out of panel div', async ({ page, gotoDashboardPage }) => { + await gotoDashboardPage({ + uid: DASHBOARD_ID, }); - test('Does not add iframes to body', async ({ page, gotoDashboardPage }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - }); + // this button reaches out of the panel div and modifies the element dataset + await page.locator('[data-testid="button-reach-out"]').click(); + await expect(page.locator('[data-sandbox-test="true"]')).toBeHidden(); + }); - // this button adds 3 iframes to the body - await page.locator('[data-testid="button-create-iframes"]').click(); - - const iframeIds = [ - 'createElementIframe', - 'innerHTMLIframe', - 'appendIframe', - 'prependIframe', - 'afterIframe', - 'beforeIframe', - 'outerHTMLIframe', - 'parseFromStringIframe', - 'insertBeforeIframe', - 'replaceChildIframe', - ]; - - for (const id of iframeIds) { - await expect(page.locator(`#${id}`)).toBeHidden(); - } + test('Does not Reaches out of the panel editor', async ({ gotoDashboardPage, page }) => { + await gotoDashboardPage({ + uid: DASHBOARD_ID, + queryParams: new URLSearchParams({ editPanel: '1' }), }); - test('Does not reaches out of panel div', async ({ page, gotoDashboardPage }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - }); + const input = page.locator('[data-testid="panel-editor-custom-editor-input"]'); + await expect(input).toBeEnabled(); - // this button reaches out of the panel div and modifies the element dataset - await page.locator('[data-testid="button-reach-out"]').click(); - await expect(page.locator('[data-sandbox-test="true"]')).toBeHidden(); + await input.fill('x'); + await expect(page.locator('[data-sandbox-test="panel-editor"]')).toBeHidden(); + }); + + test('Can access specific window global variables', async ({ page, gotoDashboardPage }) => { + await gotoDashboardPage({ + uid: DASHBOARD_ID, }); - test('Does not Reaches out of the panel editor', async ({ gotoDashboardPage, page }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - queryParams: new URLSearchParams({ editPanel: '1' }), - }); - - const input = page.locator('[data-testid="panel-editor-custom-editor-input"]'); - await expect(input).toBeEnabled(); - - await input.fill('x'); - await expect(page.locator('[data-sandbox-test="panel-editor"]')).toBeHidden(); - }); - - test('Can access specific window global variables', async ({ page, gotoDashboardPage }) => { - await gotoDashboardPage({ - uid: DASHBOARD_ID, - }); - - await page.locator('[data-testid="button-test-globals"]').click(); - await expect(page.locator('[data-sandbox-global="Prism"]')).toBeVisible(); - await expect(page.locator('[data-sandbox-global="jQuery"]')).toBeVisible(); - await expect(page.locator('[data-sandbox-global="location"]')).toBeVisible(); - }); + await page.locator('[data-testid="button-test-globals"]').click(); + await expect(page.locator('[data-sandbox-global="Prism"]')).toBeVisible(); + await expect(page.locator('[data-sandbox-global="jQuery"]')).toBeVisible(); + await expect(page.locator('[data-sandbox-global="location"]')).toBeVisible(); }); } ); diff --git a/e2e-playwright/panels-suite/status-history.spec.ts b/e2e-playwright/panels-suite/status-history.spec.ts new file mode 100644 index 00000000000..e6c7cccac67 --- /dev/null +++ b/e2e-playwright/panels-suite/status-history.spec.ts @@ -0,0 +1,87 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +const DASHBOARD_UID = 'a2f4ad9e-3b44-4624-8067-35f31be5d309'; + +test.use({ + viewport: { width: 1280, height: 2000 }, +}); + +test.describe('Panels test: StatusHistory', { tag: ['@panels', '@status-history'] }, () => { + test('renders successfully', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + }); + + // check that gauges are rendered + const statusHistoryUplot = page.locator('.uplot'); + await expect(statusHistoryUplot, 'panels are rendered').toHaveCount(11); + + // check that no panel errors exist + const errorInfo = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerCornerInfo('error')); + await expect(errorInfo, 'no errors in the panels').toBeHidden(); + }); + + test('"no data"', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '15' }), + }); + + const statusHistoryUplot = page.locator('.uplot'); + await expect(statusHistoryUplot, "that uplot doesn't appear").toBeHidden(); + + const emptyMessage = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage); + await expect(emptyMessage, 'that the empty text appears').toHaveText('No data'); + + // update the "No value" option and see if the panel updates + const noValueOption = dashboardPage + .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Standard options No value')) + .locator('input'); + + await noValueOption.fill('My empty value'); + await noValueOption.blur(); + await expect(emptyMessage, 'that the empty text has changed').toHaveText('My empty value'); + }); + + test('tooltip interactions', async ({ gotoDashboardPage, page, selectors }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '13' }), + }); + + const statusHistoryUplot = page.locator('.uplot'); + await expect(statusHistoryUplot, 'uplot is rendered').toBeVisible(); + + const tooltip = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Tooltip.Wrapper); + + // hover over a spot to trigger the tooltip + await statusHistoryUplot.hover({ position: { x: 100, y: 50 } }); + await expect(tooltip, 'tooltip appears on hover').toBeVisible(); + await expect(tooltip, 'tooltip displays the value').toContainText('value5'); + + // click to pin the tooltip, hover away to be sure it's pinned + await statusHistoryUplot.click({ position: { x: 100, y: 50 } }); + await statusHistoryUplot.hover({ position: { x: 300, y: 50 } }); + await expect(tooltip, 'tooltip pinned on click').toBeVisible(); + await expect(tooltip, 'tooltip displays the first value').toContainText('value5'); + + // unpin the tooltip, ensure it closes on hover away + await statusHistoryUplot.click({ position: { x: 300, y: 50 } }); + await statusHistoryUplot.blur(); + await expect(tooltip, 'tooltip closed after unpinning and hovering away').toBeHidden(); + + // test clicking the "x" as well + await statusHistoryUplot.click({ position: { x: 100, y: 50 } }); + await expect(tooltip, 'tooltip appears on click').toBeVisible(); + await dashboardPage.getByGrafanaSelector(selectors.components.Portal.container).getByLabel('Close').click(); + await expect(tooltip, 'tooltip closed on "x" click').toBeHidden(); + + // disable tooltips + await dashboardPage + .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Tooltip Tooltip mode')) + .getByLabel('Hidden') + .click(); + await statusHistoryUplot.hover({ position: { x: 100, y: 50 } }); + await expect(tooltip, 'tooltip is not shown when disabled').toBeHidden(); + }); +}); diff --git a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts index 7312027d3b6..a14085aa753 100644 --- a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts +++ b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts @@ -65,11 +65,11 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100); // test that hover overflow works. - const loremIpsumCell = await getCell(page, 1, longTextColIdx); + const loremIpsumCell = getCell(page, 1, longTextColIdx); await loremIpsumCell.scrollIntoViewIfNeeded(); await loremIpsumCell.hover(); await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100); - await (await getCell(page, 1, longTextColIdx + 1)).hover(); + await getCell(page, 1, longTextColIdx + 1).hover(); await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100); // enable cell inspect, confirm that hover no longer triggers. @@ -140,15 +140,15 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] ).toBeVisible(); // click the "State" column header to sort it. - const stateColumnHeader = await getCell(page, 0, 1); + const stateColumnHeader = getCell(page, 0, 1); await stateColumnHeader.getByText('Info').click(); await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'ascending'); - expect(getCell(page, 1, 1)).resolves.toContainText('down'); // down or down fast + await expect(getCell(page, 1, 1)).toContainText('down'); // down or down fast await stateColumnHeader.getByText('Info').click(); await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending'); - expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast + await expect(getCell(page, 1, 1)).toContainText('up'); // up or up fast await stateColumnHeader.getByText('Info').click(); await expect(stateColumnHeader).not.toHaveAttribute('aria-sort'); @@ -171,7 +171,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] const stateColumnHeader = page.getByRole('columnheader').nth(infoColumnIdx); // get the first value in the "State" column, filter it out, then check that it went away. - const firstStateValue = (await (await getCell(page, 1, infoColumnIdx)).textContent())!; + const firstStateValue = (await getCell(page, 1, infoColumnIdx).textContent())!; await stateColumnHeader.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton).click(); const filterContainer = dashboardPage.getByGrafanaSelector( selectors.components.Panels.Visualization.TableNG.Filters.Container @@ -188,7 +188,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] await expect(filterContainer).not.toBeVisible(); // did it actually filter out our value? - await expect(getCell(page, 1, infoColumnIdx)).resolves.not.toHaveText(firstStateValue); + await expect(getCell(page, 1, infoColumnIdx)).not.toHaveText(firstStateValue); }); test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => { @@ -289,7 +289,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] const dataLinkColIdx = await getColumnIdx(page, 'Data Link'); // Info column has a single DataLink by default. - const infoCell = await getCell(page, 1, infoColumnIdx); + const infoCell = getCell(page, 1, infoColumnIdx); await expect(infoCell.locator('a')).toBeVisible(); expect(infoCell.locator('a')).toHaveAttribute('href'); expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup'); @@ -306,7 +306,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] continue; } - const cell = await getCell(page, 1, colIdx); + const cell = getCell(page, 1, colIdx); await expect(cell.locator('a')).toBeVisible(); expect(cell.locator('a')).toHaveAttribute('href'); expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu'); @@ -319,7 +319,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] // loop thru the columns, click the links, observe that the tooltip appears, and close the tooltip. for (let colIdx = 0; colIdx < colCount; colIdx++) { - const cell = await getCell(page, 1, colIdx); + const cell = getCell(page, 1, colIdx); if (colIdx === infoColumnIdx) { // the Info column should still have its single link. expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu'); @@ -433,7 +433,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] await filterContainer.getByTitle('up', { exact: true }).locator('label').click(); await filterContainer.getByRole('button', { name: 'Ok' }).click(); - const cell = await getCell(page, 1, dataLinkColumnIdx); + const cell = getCell(page, 1, dataLinkColumnIdx); await expect(cell).toBeVisible(); await expect(cell).toHaveCSS('text-decoration', /line-through/); diff --git a/e2e-playwright/panels-suite/table-utils.ts b/e2e-playwright/panels-suite/table-utils.ts index 6a225665542..a289c0b211a 100644 --- a/e2e-playwright/panels-suite/table-utils.ts +++ b/e2e-playwright/panels-suite/table-utils.ts @@ -1,6 +1,6 @@ import { Page, Locator } from '@playwright/test'; -export const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) => +export const getCell = (loc: Page | Locator, rowIdx: number, colIdx: number) => loc .getByRole('row') .nth(rowIdx) @@ -8,7 +8,7 @@ export const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: numbe .nth(colIdx); export const getCellHeight = async (loc: Page | Locator, rowIdx: number, colIdx: number) => { - const cell = await getCell(loc, rowIdx, colIdx); + const cell = getCell(loc, rowIdx, colIdx); return (await cell.boundingBox())?.height ?? 0; }; @@ -18,7 +18,7 @@ export const getColumnIdx = async (loc: Page | Locator, columnName: string) => { let result = -1; const colCount = await loc.getByRole('columnheader').count(); for (let colIdx = 0; colIdx < colCount; colIdx++) { - const cell = await getCell(loc, 0, colIdx); + const cell = getCell(loc, 0, colIdx); if ((await cell.textContent()) === columnName) { result = colIdx; break; diff --git a/e2e-playwright/panels-suite/timeseries-mouse-zoom-interaction.spec.ts b/e2e-playwright/panels-suite/timeseries-mouse-zoom-interaction.spec.ts new file mode 100644 index 00000000000..c881151f656 --- /dev/null +++ b/e2e-playwright/panels-suite/timeseries-mouse-zoom-interaction.spec.ts @@ -0,0 +1,93 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +const DASHBOARD_UID = 'TkZXxlNG3'; + +test.describe('Panels test: Timeseries zoom interaction', { tag: ['@panels', '@timeseries'] }, () => { + test('shows zoom cursor during x-axis drag-to-zoom interaction', async ({ gotoDashboardPage, page }) => { + await gotoDashboardPage({ + uid: DASHBOARD_UID, + }); + + const uplotOverlay = page.locator('.u-over').first(); + await expect(uplotOverlay, 'uplot overlay is visible').toBeVisible(); + + const bbox = await uplotOverlay.boundingBox(); + expect(bbox, 'uplot overlay has dimensions').toBeDefined(); + + await expect(uplotOverlay, 'no zoom cursor initially').not.toHaveClass(/zoom-drag/); + + await page.mouse.move(bbox!.x + 100, bbox!.y + 50); + await page.mouse.down(); + + await expect(uplotOverlay, 'zoom cursor appears on mousedown').toHaveClass(/zoom-drag/); + + await page.mouse.move(bbox!.x + 300, bbox!.y + 50); + + await expect(uplotOverlay, 'zoom cursor persists during drag').toHaveClass(/zoom-drag/); + + await page.mouse.up(); + + await expect(uplotOverlay, 'zoom cursor removed after mouseup').not.toHaveClass(/zoom-drag/); + }); + + test('shows zoom cursor during y-axis drag-to-zoom interaction with Shift key', async ({ + gotoDashboardPage, + page, + }) => { + await gotoDashboardPage({ + uid: DASHBOARD_UID, + }); + + const uplotOverlay = page.locator('.u-over').first(); + await expect(uplotOverlay, 'uplot overlay is visible').toBeVisible(); + + const bbox = await uplotOverlay.boundingBox(); + expect(bbox, 'uplot overlay has dimensions').toBeDefined(); + + await expect(uplotOverlay, 'no zoom cursor initially').not.toHaveClass(/zoom-drag/); + + await page.keyboard.down('Shift'); + await page.mouse.move(bbox!.x + 100, bbox!.y + 50); + await page.mouse.down(); + + await expect(uplotOverlay, 'zoom cursor appears on mousedown with Shift').toHaveClass(/zoom-drag/); + + await page.mouse.move(bbox!.x + 100, bbox!.y + 150); + + await expect(uplotOverlay, 'zoom cursor persists during drag with Shift').toHaveClass(/zoom-drag/); + + await page.mouse.up(); + await page.keyboard.up('Shift'); + + await expect(uplotOverlay, 'zoom cursor removed after mouseup').not.toHaveClass(/zoom-drag/); + }); + + test('does not show zoom cursor when modifier keys are pressed', async ({ gotoDashboardPage, page }) => { + await gotoDashboardPage({ + uid: DASHBOARD_UID, + }); + + const uplotOverlay = page.locator('.u-over').first(); + await expect(uplotOverlay, 'uplot overlay is visible').toBeVisible(); + + const bbox = await uplotOverlay.boundingBox(); + + await page.keyboard.down('Control'); + await page.mouse.move(bbox!.x + 100, bbox!.y + 50); + await page.mouse.down(); + + await expect(uplotOverlay, 'no zoom cursor with Ctrl key').not.toHaveClass(/zoom-drag/); + + await page.mouse.up(); + await page.keyboard.up('Control'); + + await page.keyboard.down('Meta'); + await page.mouse.move(bbox!.x + 100, bbox!.y + 50); + await page.mouse.down(); + + await expect(uplotOverlay, 'no zoom cursor with Meta key').not.toHaveClass(/zoom-drag/); + + await page.mouse.up(); + await page.keyboard.up('Meta'); + }); +}); diff --git a/e2e-playwright/panels-suite/timeseries.spec.ts b/e2e-playwright/panels-suite/timeseries.spec.ts new file mode 100644 index 00000000000..fb2124f7dda --- /dev/null +++ b/e2e-playwright/panels-suite/timeseries.spec.ts @@ -0,0 +1,119 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +const DASHBOARD_UID = '1KxMUdE7k'; + +test.use({ + featureToggles: { + timeRangePan: true, + }, +}); + +test.describe('Panels test: TimeSeries X-axis panning', { tag: ['@panels', '@timeseries'] }, () => { + test('cursor changes to grab hand over x-axis', async ({ gotoDashboardPage, page }) => { + await test.step('Load dashboard and verify cursor changes to grab', async () => { + const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UID }); + + const timeseriesPanel = page.locator('.uplot').first(); + await expect(timeseriesPanel, 'panel rendered').toBeVisible(); + + const xAxis = timeseriesPanel.locator('.u-axis').first(); + await expect(xAxis, 'x-axis rendered').toBeVisible(); + + await xAxis.hover(); + + const cursorStyle = await xAxis.evaluate((el: HTMLElement) => window.getComputedStyle(el).cursor); + expect(cursorStyle, 'cursor is grab').toBe('grab'); + }); + }); + + test('drag right pans backward in time, drag left pans forward', async ({ gotoDashboardPage, page, selectors }) => { + let centerX: number; + let centerY: number; + let initialFromTime: number; + let initialToTime: number; + + const dashboardPage = await test.step('Load dashboard and capture initial time range', async () => { + const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UID }); + + const timeseriesPanel = page.locator('.uplot').first(); + await expect(timeseriesPanel, 'panel rendered').toBeVisible(); + + const xAxis = timeseriesPanel.locator('.u-axis').first(); + await expect(xAxis, 'x-axis rendered').toBeVisible(); + + const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton); + await timePickerButton.click(); + + const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField); + const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField); + + const initialFrom = await fromField.inputValue(); + const initialTo = await toField.inputValue(); + initialFromTime = new Date(initialFrom).getTime(); + initialToTime = new Date(initialTo).getTime(); + + await page.keyboard.press('Escape'); + + const axisBox = await xAxis.boundingBox(); + if (!axisBox) { + throw new Error('X-axis bounding box not found'); + } + + centerX = axisBox.x + axisBox.width / 2; + centerY = axisBox.y + axisBox.height / 2; + + return dashboardPage; + }); + + await test.step('Drag right pans backward in time', async () => { + await page.mouse.move(centerX, centerY); + await page.mouse.down(); + await page.mouse.move(centerX + 100, centerY); + await page.mouse.up(); + + await page.waitForTimeout(1000); + + const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton); + await timePickerButton.click(); + + const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField); + const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField); + + const afterRightFrom = await fromField.inputValue(); + const afterRightTo = await toField.inputValue(); + const afterRightFromTime = new Date(afterRightFrom).getTime(); + const afterRightToTime = new Date(afterRightTo).getTime(); + + expect(afterRightFromTime, 'panned backward').toBeLessThan(initialFromTime); + expect(afterRightToTime, 'panned backward').toBeLessThan(initialToTime); + + await page.keyboard.press('Escape'); + + initialFromTime = afterRightFromTime; + initialToTime = afterRightToTime; + }); + + await test.step('Drag left pans forward in time', async () => { + await page.mouse.move(centerX, centerY); + await page.mouse.down(); + await page.mouse.move(centerX - 100, centerY); + await page.mouse.up(); + + await page.waitForTimeout(1000); + + const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton); + await timePickerButton.click(); + + const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField); + const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField); + + const afterLeftFrom = await fromField.inputValue(); + const afterLeftTo = await toField.inputValue(); + const afterLeftFromTime = new Date(afterLeftFrom).getTime(); + const afterLeftToTime = new Date(afterLeftTo).getTime(); + + expect(afterLeftFromTime, 'panned forward').toBeGreaterThan(initialFromTime); + expect(afterLeftToTime, 'panned forward').toBeGreaterThan(initialToTime); + }); + }); +}); diff --git a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts index e1fef705a88..0133a3e3712 100644 --- a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts +++ b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts @@ -38,7 +38,7 @@ test.describe( formatExpectError('Could not locate header elements in table panel') ).toContainText(['col1', 'col2']); await expect( - panelEditPage.panel.locator.getByRole('gridcell'), + panelEditPage.panel.data, formatExpectError('Could not locate headers in table panel') ).toContainText(['val1', 'val2', 'val3', 'val4']); }); @@ -58,7 +58,7 @@ test.describe( formatExpectError('Could not locate header elements in table panel') ).toContainText(['col1', 'col2']); await expect( - panelEditPage.panel.locator.getByRole('gridcell'), + panelEditPage.panel.data, formatExpectError('Could not locate data elements in table panel') ).toContainText(['val1', 'val2', 'val3', 'val4']); }); diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json index 12829268e71..0b9f45a25a3 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/extensions-test-app", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "private": true, "scripts": { "build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production", diff --git a/e2e-playwright/test-plugins/grafana-test-datasource/package.json b/e2e-playwright/test-plugins/grafana-test-datasource/package.json index e62edcbaad6..8d97d03dc30 100644 --- a/e2e-playwright/test-plugins/grafana-test-datasource/package.json +++ b/e2e-playwright/test-plugins/grafana-test-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/grafana-e2etest-datasource", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "private": true, "scripts": { "build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production", diff --git a/e2e-playwright/various-suite/frontend-sandbox-app.spec.ts b/e2e-playwright/various-suite/frontend-sandbox-app.spec.ts index dfa19753b15..15fcdb8016c 100644 --- a/e2e-playwright/various-suite/frontend-sandbox-app.spec.ts +++ b/e2e-playwright/various-suite/frontend-sandbox-app.spec.ts @@ -17,60 +17,24 @@ test.describe( }); test.describe('App Page', () => { - test.describe('Sandbox disabled', () => { - test.beforeEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); - }); - }); + test('Loads the app page with the sandbox div wrapper', async ({ page }) => { + await page.goto(`/a/${APP_ID}`); - test('Loads the app page without the sandbox div wrapper', async ({ page }) => { - await page.goto(`/a/${APP_ID}`); + const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]'); + await expect(sandboxDiv).toBeVisible(); - const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]'); - await expect(sandboxDiv).toBeHidden(); - - const appPage = page.getByTestId('sandbox-app-test-page-one'); - await expect(appPage).toBeVisible(); - }); - - test('Loads the app configuration without the sandbox div wrapper', async ({ page }) => { - await page.goto(`/plugins/${APP_ID}`); - - const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]'); - await expect(sandboxDiv).toBeHidden(); - - const configPage = page.getByTestId('sandbox-app-test-config-page'); - await expect(configPage).toBeVisible(); - }); + const appPage = page.getByTestId('sandbox-app-test-page-one'); + await expect(appPage).toBeVisible(); }); - test.describe('Sandbox enabled', () => { - test.beforeEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); - }); - }); + test('Loads the app configuration with the sandbox div wrapper', async ({ page }) => { + await page.goto(`/plugins/${APP_ID}`); - test('Loads the app page with the sandbox div wrapper', async ({ page }) => { - await page.goto(`/a/${APP_ID}`); + const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]'); + await expect(sandboxDiv).toBeVisible(); - const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]'); - await expect(sandboxDiv).toBeVisible(); - - const appPage = page.getByTestId('sandbox-app-test-page-one'); - await expect(appPage).toBeVisible(); - }); - - test('Loads the app configuration with the sandbox div wrapper', async ({ page }) => { - await page.goto(`/plugins/${APP_ID}`); - - const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]'); - await expect(sandboxDiv).toBeVisible(); - - const configPage = page.getByTestId('sandbox-app-test-config-page'); - await expect(configPage).toBeVisible(); - }); + const configPage = page.getByTestId('sandbox-app-test-config-page'); + await expect(configPage).toBeVisible(); }); }); } diff --git a/e2e-playwright/various-suite/frontend-sandbox-datasource.spec.ts b/e2e-playwright/various-suite/frontend-sandbox-datasource.spec.ts index acddc23018c..339d3c24c19 100644 --- a/e2e-playwright/various-suite/frontend-sandbox-datasource.spec.ts +++ b/e2e-playwright/various-suite/frontend-sandbox-datasource.spec.ts @@ -11,246 +11,127 @@ test.describe( }, () => { test.describe('Config Editor', () => { - test.describe('Sandbox disabled', () => { - test.beforeEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); - }); + test('Should render a sandbox wrapper around the datasource config editor', async ({ + page, + createDataSource, + }) => { + const TIMESTAMP = Date.now(); + const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; + // Add the datasource + const response = await createDataSource({ + type: DATASOURCE_ID, + name: DATASOURCE_TYPED_NAME, }); + const DATASOURCE_CONNECTION_ID = response.uid; + await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); - test('Should not render a sandbox wrapper around the datasource config editor', async ({ - page, - createDataSource, - }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); - - const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); - await expect(sandboxDiv).toBeHidden(); - }); + const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); + await expect(sandboxDiv).toBeVisible(); }); - test.describe('Sandbox enabled', () => { - test.beforeEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); - }); + test('Should store values in jsonData and secureJsonData correctly', async ({ page, createDataSource }) => { + const TIMESTAMP = Date.now(); + const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; + // Add the datasource + const response = await createDataSource({ + type: DATASOURCE_ID, + name: DATASOURCE_TYPED_NAME, }); + const DATASOURCE_CONNECTION_ID = response.uid; + await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); - test('Should render a sandbox wrapper around the datasource config editor', async ({ - page, - createDataSource, - }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); + const valueToStore = 'test' + random(100); - const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); - await expect(sandboxDiv).toBeVisible(); - }); + const queryInput = page.locator('[data-testid="sandbox-config-editor-query-input"]'); + await expect(queryInput).not.toBeDisabled(); + await queryInput.fill(valueToStore); + await expect(queryInput).toHaveValue(valueToStore); - test('Should store values in jsonData and secureJsonData correctly', async ({ page, createDataSource }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); + const saveButton = page.getByTestId('data-testid Data source settings page Save and Test button'); + await saveButton.click(); - const valueToStore = 'test' + random(100); + const alert = page.getByTestId('data-testid Data source settings page Alert'); + await expect(alert).toBeVisible(); + await expect(alert).toContainText('Sandbox Success'); - const queryInput = page.locator('[data-testid="sandbox-config-editor-query-input"]'); - await expect(queryInput).not.toBeDisabled(); - await queryInput.fill(valueToStore); - await expect(queryInput).toHaveValue(valueToStore); - - const saveButton = page.getByTestId('data-testid Data source settings page Save and Test button'); - await saveButton.click(); - - const alert = page.getByTestId('data-testid Data source settings page Alert'); - await expect(alert).toBeVisible(); - await expect(alert).toContainText('Sandbox Success'); - - // validate the value was stored - await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); - await expect(queryInput).not.toBeDisabled(); - await expect(queryInput).toHaveValue(valueToStore); - }); + // validate the value was stored + await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); + await expect(queryInput).not.toBeDisabled(); + await expect(queryInput).toHaveValue(valueToStore); }); }); test.describe('Explore Page', () => { - test.describe('Sandbox disabled', () => { - test.beforeEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); - }); + test('Should wrap the query editor in a sandbox wrapper', async ({ + page, + createDataSource, + dashboardPage, + selectors, + }) => { + const TIMESTAMP = Date.now(); + const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; + // Add the datasource + const response = await createDataSource({ + type: DATASOURCE_ID, + name: DATASOURCE_TYPED_NAME, }); + const DATASOURCE_CONNECTION_ID = response.uid; + await page.goto('/explore'); - test('Should not wrap the query editor in a sandbox wrapper', async ({ - page, - createDataSource, - dashboardPage, - selectors, - }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto('/explore'); + const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); + await expect(dataSourcePicker).toBeVisible(); + await dataSourcePicker.click(); - const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); - await expect(dataSourcePicker).toBeVisible(); - await dataSourcePicker.click(); + const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`); + await expect(datasourceOption).toBeVisible(); + await datasourceOption.scrollIntoViewIfNeeded(); + await datasourceOption.click(); - const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`); - await expect(datasourceOption).toBeVisible(); - await datasourceOption.scrollIntoViewIfNeeded(); - await datasourceOption.click(); + // make sure the datasource was correctly selected and rendered + const breadcrumb = dashboardPage.getByGrafanaSelector( + selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME) + ); + await expect(breadcrumb).toBeVisible(); - // make sure the datasource was correctly selected and rendered - const breadcrumb = dashboardPage.getByGrafanaSelector( - selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME) - ); - await expect(breadcrumb).toBeVisible(); - - const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); - await expect(sandboxDiv).toBeHidden(); - }); - - test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto('/explore'); - - const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); - await expect(dataSourcePicker).toBeVisible(); - await dataSourcePicker.click(); - - const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`); - await expect(datasourceOption).toBeVisible(); - await datasourceOption.scrollIntoViewIfNeeded(); - await datasourceOption.click(); - - // make sure the datasource was correctly selected and rendered - const breadcrumb = dashboardPage.getByGrafanaSelector( - selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME) - ); - await expect(breadcrumb).toBeVisible(); - - const valueToType = 'test' + random(100); - - const queryInput = page.locator('[data-testid="sandbox-query-editor-query-input"]'); - await expect(queryInput).not.toBeDisabled(); - await queryInput.fill(valueToType); - await expect(queryInput).toHaveValue(valueToType); - }); + const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); + await expect(sandboxDiv).toBeVisible(); }); - test.describe('Sandbox enabled', () => { - test.beforeEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); - }); + test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => { + const TIMESTAMP = Date.now(); + const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; + // Add the datasource + const response = await createDataSource({ + type: DATASOURCE_ID, + name: DATASOURCE_TYPED_NAME, }); + const DATASOURCE_CONNECTION_ID = response.uid; + await page.goto('/explore'); - test('Should wrap the query editor in a sandbox wrapper', async ({ - page, - createDataSource, - dashboardPage, - selectors, - }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto('/explore'); + const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); + await expect(dataSourcePicker).toBeVisible(); + await dataSourcePicker.click(); - const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); - await expect(dataSourcePicker).toBeVisible(); - await dataSourcePicker.click(); + const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`); + await expect(datasourceOption).toBeVisible(); + await datasourceOption.scrollIntoViewIfNeeded(); + await datasourceOption.click(); - const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`); - await expect(datasourceOption).toBeVisible(); - await datasourceOption.scrollIntoViewIfNeeded(); - await datasourceOption.click(); + // make sure the datasource was correctly selected and rendered + const breadcrumb = dashboardPage.getByGrafanaSelector( + selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME) + ); + await expect(breadcrumb).toBeVisible(); - // make sure the datasource was correctly selected and rendered - const breadcrumb = dashboardPage.getByGrafanaSelector( - selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME) - ); - await expect(breadcrumb).toBeVisible(); + const valueToType = 'test' + random(100); - const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); - await expect(sandboxDiv).toBeVisible(); - }); + const queryInput = page.locator('[data-testid="sandbox-query-editor-query-input"]'); + await expect(queryInput).not.toBeDisabled(); + await queryInput.fill(valueToType); + await expect(queryInput).toHaveValue(valueToType); - test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => { - const TIMESTAMP = Date.now(); - const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`; - // Add the datasource - const response = await createDataSource({ - type: DATASOURCE_ID, - name: DATASOURCE_TYPED_NAME, - }); - const DATASOURCE_CONNECTION_ID = response.uid; - await page.goto('/explore'); - - const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); - await expect(dataSourcePicker).toBeVisible(); - await dataSourcePicker.click(); - - const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`); - await expect(datasourceOption).toBeVisible(); - await datasourceOption.scrollIntoViewIfNeeded(); - await datasourceOption.click(); - - // make sure the datasource was correctly selected and rendered - const breadcrumb = dashboardPage.getByGrafanaSelector( - selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME) - ); - await expect(breadcrumb).toBeVisible(); - - const valueToType = 'test' + random(100); - - const queryInput = page.locator('[data-testid="sandbox-query-editor-query-input"]'); - await expect(queryInput).not.toBeDisabled(); - await queryInput.fill(valueToType); - await expect(queryInput).toHaveValue(valueToType); - - // typing the query editor should reflect in the url - await expect(page).toHaveURL(new RegExp(valueToType)); - }); + // typing the query editor should reflect in the url + await expect(page).toHaveURL(new RegExp(valueToType)); }); }); diff --git a/e2e/cypress/fixtures/long-trace-response-backend.json b/e2e/cypress/fixtures/long-trace-response-backend.json index 5605ab1dd7c..56163b4b3d5 100644 --- a/e2e/cypress/fixtures/long-trace-response-backend.json +++ b/e2e/cypress/fixtures/long-trace-response-backend.json @@ -167,6 +167,16 @@ "3fa414edcef6ad90", "3fa414edcef6ad90", "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", + "3fa414edcef6ad90", "3fa414edcef6ad90" ], [ @@ -289,7 +299,17 @@ "0000000000000074", "0000000000000075", "0000000000000076", - "0000000000000077" + "0000000000000077", + "0000000000000078", + "0000000000000079", + "000000000000007a", + "000000000000007b", + "000000000000007c", + "000000000000007d", + "000000000000007e", + "000000000000007f", + "0000000000000080", + "0000000000000081" ], [ "", @@ -411,7 +431,17 @@ "0000000000000039", "000000000000003a", "000000000000003a", - "000000000000003b" + "000000000000003b", + "000000000000003b", + "0000000000000078", + "0000000000000078", + "0000000000000079", + "0000000000000079", + "000000000000007a", + "000000000000007a", + "000000000000007b", + "000000000000007b", + "000000000000007c" ], [ "GET /api/health", @@ -533,7 +563,17 @@ "GET /rollback/prepare", "POST /commit/save", "GET /branch/merge", - "POST /tag/create" + "POST /tag/create", + "GET /release/notes", + "POST /changelog/generate", + "GET /documentation/build", + "POST /test/run", + "GET /coverage/report", + "POST /artifact/publish", + "GET /registry/pull", + "POST /image/push", + "GET /manifest/inspect", + "POST /deployment/verify" ], [ "api-gateway", @@ -655,7 +695,17 @@ "rollback-mgr", "git-api", "merge-api", - "tag-api" + "tag-api", + "release-api", + "changelog-gen", + "doc-builder", + "test-runner", + "coverage-tool", + "artifact-mgr", + "registry-client", + "image-pusher", + "manifest-parser", + "deploy-verifier" ], [ 1579270400000, 1579270401000, 1579270402000, 1579270403000, 1579270404000, 1579270405000, 1579270406000, @@ -675,7 +725,8 @@ 1579270498000, 1579270499000, 1579270500000, 1579270501000, 1579270502000, 1579270503000, 1579270504000, 1579270505000, 1579270506000, 1579270507000, 1579270508000, 1579270509000, 1579270510000, 1579270511000, 1579270512000, 1579270513000, 1579270514000, 1579270515000, 1579270516000, 1579270517000, 1579270518000, - 1579270519000 + 1579270519000, 1579270520000, 1579270521000, 1579270522000, 1579270523000, 1579270524000, 1579270525000, + 1579270526000, 1579270527000, 1579270528000, 1579270529000 ], [ 100000, 50000, 75000, 200000, 25000, 150000, 80000, 300000, 45000, 120000, 60000, 180000, 35000, 250000, @@ -686,7 +737,8 @@ 190000, 58000, 145000, 92000, 220000, 48000, 175000, 85000, 205000, 65000, 180000, 72000, 195000, 55000, 160000, 98000, 225000, 62000, 140000, 78000, 200000, 48000, 165000, 85000, 215000, 58000, 150000, 88000, 230000, 45000, 175000, 72000, 185000, 62000, 155000, 95000, 210000, 52000, 170000, 82000, 195000, 68000, - 145000, 92000, 235000, 58000, 160000, 75000, 180000, 65000 + 145000, 92000, 235000, 58000, 160000, 75000, 180000, 65000, 125000, 78000, 190000, 52000, 165000, 88000, + 215000, 62000, 155000, 95000 ] ] } diff --git a/e2e/old-arch/various-suite/trace-view-scrolling.spec.ts b/e2e/old-arch/various-suite/trace-view-scrolling.spec.ts index f73c3c6f2c1..372c2d11032 100644 --- a/e2e/old-arch/various-suite/trace-view-scrolling.spec.ts +++ b/e2e/old-arch/various-suite/trace-view-scrolling.spec.ts @@ -33,15 +33,15 @@ describe('Trace view', () => { e2e.components.TraceViewer.spanBar().should('be.visible'); + e2e.components.TraceViewer.spanBar().its('length').should('be.equal', 100); + + e2e.pages.Explore.General.scrollView().children().first().scrollTo('bottom'); + + // After scrolling we should see 50 spans e2e.components.TraceViewer.spanBar() .its('length') - .then((oldLength) => { - e2e.pages.Explore.General.scrollView().children().first().scrollTo('center'); - - // After scrolling we should load more spans - e2e.components.TraceViewer.spanBar().should(($span) => { - expect($span.length).to.be.gt(oldLength); - }); + .should(($span) => { + expect($span).to.be.equal(50); }); }); }); diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 18367de0c3f..bcb6d105d35 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -24,11 +24,6 @@ "count": 1 } }, - "packages/grafana-data/src/dataframe/ArrayDataFrame.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "packages/grafana-data/src/dataframe/CircularDataFrame.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -36,7 +31,7 @@ }, "packages/grafana-data/src/dataframe/DataFrameView.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 2 + "count": 1 }, "@typescript-eslint/no-explicit-any": { "count": 2 @@ -44,28 +39,23 @@ }, "packages/grafana-data/src/dataframe/MutableDataFrame.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 3 + "count": 1 }, "@typescript-eslint/no-explicit-any": { - "count": 9 + "count": 6 } }, "packages/grafana-data/src/dataframe/StreamingDataFrame.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 4 - } - }, - "packages/grafana-data/src/dataframe/processDataFrame.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 + "count": 3 } }, "packages/grafana-data/src/dataframe/processDataFrame.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 15 + "count": 11 }, "@typescript-eslint/no-explicit-any": { - "count": 5 + "count": 4 } }, "packages/grafana-data/src/datetime/moment_wrapper.ts": { @@ -106,7 +96,7 @@ "count": 1 }, "@typescript-eslint/no-explicit-any": { - "count": 5 + "count": 4 } }, "packages/grafana-data/src/panel/registryFactories.ts": { @@ -247,7 +237,7 @@ }, "packages/grafana-data/src/types/panel.ts": { "@typescript-eslint/no-explicit-any": { - "count": 13 + "count": 11 } }, "packages/grafana-data/src/types/plugin.ts": { @@ -305,7 +295,7 @@ }, "packages/grafana-data/src/utils/csv.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 3 + "count": 2 }, "@typescript-eslint/no-explicit-any": { "count": 2 @@ -347,11 +337,6 @@ "count": 3 } }, - "packages/grafana-data/src/vector/CircularVector.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "packages/grafana-data/src/vector/FunctionalVector.ts": { "@typescript-eslint/no-explicit-any": { "count": 9 @@ -597,14 +582,6 @@ "count": 5 } }, - "packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx": { - "no-restricted-syntax": { - "count": 1 - }, - "react-hooks/rules-of-hooks": { - "count": 5 - } - }, "packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -625,32 +602,14 @@ "count": 1 } }, - "packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx": { - "no-restricted-syntax": { - "count": 3 - } - }, "packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 }, - "no-restricted-syntax": { - "count": 3 - }, "react-prefer-function-component/react-prefer-function-component": { "count": 1 } }, - "packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx": { - "no-restricted-syntax": { - "count": 3 - } - }, - "packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx": { - "no-restricted-syntax": { - "count": 3 - } - }, "packages/grafana-ui/src/components/DataSourceSettings/types.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -777,7 +736,7 @@ }, "packages/grafana-ui/src/components/Modal/ModalsContext.tsx": { "@typescript-eslint/no-explicit-any": { - "count": 4 + "count": 2 }, "react-prefer-function-component/react-prefer-function-component": { "count": 1 @@ -793,11 +752,6 @@ "count": 1 } }, - "packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "packages/grafana-ui/src/components/PanelChrome/PanelContext.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -855,7 +809,7 @@ }, "packages/grafana-ui/src/components/Select/SelectBase.tsx": { "@typescript-eslint/consistent-type-assertions": { - "count": 2 + "count": 1 }, "@typescript-eslint/no-explicit-any": { "count": 4 @@ -868,7 +822,7 @@ }, "packages/grafana-ui/src/components/Select/resetSelectStyles.ts": { "@typescript-eslint/no-explicit-any": { - "count": 4 + "count": 1 } }, "packages/grafana-ui/src/components/Select/types.ts": { @@ -883,7 +837,7 @@ }, "packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 3 + "count": 2 }, "@typescript-eslint/no-explicit-any": { "count": 13 @@ -2057,11 +2011,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -2301,10 +2250,10 @@ }, "public/app/features/dashboard/api/ResponseTransformers.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 4 + "count": 2 }, "@typescript-eslint/no-explicit-any": { - "count": 3 + "count": 1 } }, "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx": { @@ -2886,31 +2835,11 @@ "count": 1 } }, - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx": { "react-hooks/rules-of-hooks": { "count": 1 } }, - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 @@ -3313,11 +3242,6 @@ "count": 3 } }, - "public/app/features/provisioning/Config/ConfigFormGithubCollapse.tsx": { - "no-restricted-syntax": { - "count": 2 - } - }, "public/app/features/provisioning/Shared/BranchValidationError.tsx": { "react/no-unescaped-entities": { "count": 26 @@ -3328,6 +3252,23 @@ "count": 2 } }, + "public/app/features/query/components/QueryEditorRow.tsx": { + "@grafana/no-aria-label-selectors": { + "count": 1 + }, + "@typescript-eslint/consistent-type-assertions": { + "count": 3 + }, + "no-restricted-syntax": { + "count": 1 + }, + "react-hooks/rules-of-hooks": { + "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/query/components/QueryEditorRowHeader.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 @@ -4767,7 +4708,7 @@ }, "public/app/plugins/panel/live/LiveChannelEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { - "count": 2 + "count": 1 }, "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/eslint.config.js b/eslint.config.js index b88a4231fd3..2af812e1fe3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -434,6 +434,7 @@ module.exports = [ 'public/app/plugins/datasource/loki/**/*.{ts,tsx}', 'public/app/plugins/datasource/loki/**/*.{ts,tsx}', 'public/app/plugins/datasource/mysql/**/*.{ts,tsx}', + 'public/app/plugins/datasource/opentsdb/**/*.{ts,tsx}', 'public/app/plugins/datasource/parca/**/*.{ts,tsx}', 'public/app/plugins/datasource/tempo/**/*.{ts,tsx}', 'public/app/plugins/datasource/zipkin/**/*.{ts,tsx}', @@ -599,4 +600,17 @@ module.exports = [ ], }, }, + + // { + // name: 'grafana/plugin-external-import-paths', + // files: [ + // 'public/app/plugins/panel/histogram/**/*.{ts,tsx}', + // ], + // plugins: { + // '@grafana': grafanaPlugin, + // }, + // rules: { + // '@grafana/no-plugin-external-import-paths': 'error', + // }, + // }, ]; diff --git a/go.mod b/go.mod index 9657b88efca..873633a0dad 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/apache/arrow-go/v18 v18.4.1 // @grafana/plugins-platform-backend github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources - github.com/aws/aws-sdk-go-v2 v1.38.1 // @grafana/aws-datasources + github.com/aws/aws-sdk-go-v2 v1.39.1 // @grafana/aws-datasources github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 // @grafana/aws-datasources github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 // @grafana/aws-datasources github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 // @grafana/aws-datasources @@ -63,7 +63,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.2 // @grafana/identity-access-team github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group github.com/go-ldap/ldap/v3 v3.4.4 // @grafana/identity-access-team - github.com/go-logfmt/logfmt v0.6.0 // @grafana/oss-big-tent + github.com/go-logfmt/logfmt v0.6.1 // @grafana/oss-big-tent github.com/go-openapi/loads v0.23.1 // @grafana/alerting-backend github.com/go-openapi/runtime v0.28.0 // @grafana/alerting-backend github.com/go-openapi/strfmt v0.24.0 // @grafana/alerting-backend @@ -98,7 +98,7 @@ require ( github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend github.com/grafana/grafana-app-sdk v0.48.1 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-app-sdk/logging v0.48.1 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-aws-sdk v1.2.0 // @grafana/aws-datasources + github.com/grafana/grafana-aws-sdk v1.3.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-google-sdk-go v0.4.2 // @grafana/partner-datasources @@ -106,7 +106,7 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.281.0 // @grafana/plugins-platform-backend github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 // @grafana/alerting-backend github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs - github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 // indirect; @grafana/grafana-git-ui-sync-team + github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b // indirect; @grafana/grafana-git-ui-sync-team github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // @grafana/observability-traces-and-profiling github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae // @grafana/observability-traces-and-profiling @@ -172,7 +172,7 @@ require ( github.com/stretchr/testify v1.11.1 // @grafana/grafana-backend-group github.com/testcontainers/testcontainers-go v0.36.0 //@grafana/grafana-app-platform-squad github.com/thomaspoignant/go-feature-flag v1.42.0 // @grafana/grafana-backend-group - github.com/tjhop/slog-gokit v0.1.3 // @grafana/grafana-app-platform-squad + github.com/tjhop/slog-gokit v0.1.5 // @grafana/grafana-app-platform-squad github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 // @grafana/grafana-backend-group github.com/urfave/cli v1.22.17 // indirect; @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.7 // @grafana/grafana-backend-group @@ -237,6 +237,7 @@ require ( github.com/grafana/grafana/apps/alerting/alertenrichment v0.0.0 // @grafana/alerting-backend github.com/grafana/grafana/apps/alerting/notifications v0.0.0 // @grafana/alerting-backend github.com/grafana/grafana/apps/alerting/rules v0.0.0 // @grafana/alerting-backend + github.com/grafana/grafana/apps/annotation v0.0.0 // @grafana/grafana-backend-services-squad github.com/grafana/grafana/apps/correlations v0.0.0 // @grafana/datapro github.com/grafana/grafana/apps/dashboard v0.0.0 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b // @grafana/grafana-app-platform-squad @@ -268,6 +269,7 @@ replace ( github.com/grafana/grafana/apps/alerting/alertenrichment => ./apps/alerting/alertenrichment github.com/grafana/grafana/apps/alerting/notifications => ./apps/alerting/notifications github.com/grafana/grafana/apps/alerting/rules => ./apps/alerting/rules + github.com/grafana/grafana/apps/annotation => ./apps/annotation github.com/grafana/grafana/apps/correlations => ./apps/correlations github.com/grafana/grafana/apps/dashboard => ./apps/dashboard github.com/grafana/grafana/apps/folder => ./apps/folder @@ -332,23 +334,23 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/at-wat/mqtt-go v0.19.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect - github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.10 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect diff --git a/go.sum b/go.sum index 6e7d077b31f..5717702b7a3 100644 --- a/go.sum +++ b/go.sum @@ -846,22 +846,22 @@ github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= -github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE= +github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= -github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0= -github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= +github.com/aws/aws-sdk-go-v2/config v1.31.10 h1:7LllDZAegXU3yk41mwM6KcPu0wmjKGQB1bg99bNdQm4= +github.com/aws/aws-sdk-go-v2/config v1.31.10/go.mod h1:Ge6gzXPjqu4v0oHvgAwvGzYcK921GU0hQM25WF/Kl+8= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 h1:gLD09eaJUdiszm7vd1btiQUYE0Hj+0I2b8AS+75z9AY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8/go.mod h1:4RW3oMPt1POR74qVOC4SbubxAwdP4pCT0nSw3jycOU4= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs= @@ -872,12 +872,12 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFie github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0/go.mod h1:UseIHRfrm7PqeZo6fcTb6FUCXzCnh1KJbQbmOfxArGM= github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJdQgRP5mKCTDt4GNk= github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U= github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 h1:zJeUxFP7+XP52u23vrp4zMcVhShTWbNO8dHV6xCSvFo= @@ -888,12 +888,12 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 h1:Pwbxovp github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6/go.mod h1:Z4xLt5mXspLKjBV92i165wAJ/3T6TIv4n7RtIS8pWV0= github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg= github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 h1:FTdEN9dtWPB0EOURNtDPmwGp6GGvMqRJCAihkSl/1No= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.4/go.mod h1:mYubxV9Ff42fZH4kexj43gFPhgc/LyC7KqvUKt1watc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 h1:I7ghctfGXrscr7r1Ga/mDqSJKm7Fkpl5Mwq79Z+rZqU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0/go.mod h1:Zo9id81XP6jbayIFWNuDpA6lMBWhsVy+3ou2jLa4JnA= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0= github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s= @@ -1248,8 +1248,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= +github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -1637,8 +1637,8 @@ github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+ github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c= github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= -github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw= -github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A= +github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= +github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc= github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI= @@ -1661,8 +1661,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLm github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78= github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw= github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y= -github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8= -github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= +github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg= +github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= @@ -2499,8 +2499,8 @@ github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= -github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= +github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc= +github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= diff --git a/go.work b/go.work index 3fa612c2fce..c1baa393967 100644 --- a/go.work +++ b/go.work @@ -9,6 +9,7 @@ use ( ./apps/alerting/alertenrichment ./apps/alerting/notifications ./apps/alerting/rules + ./apps/annotation ./apps/correlations ./apps/dashboard ./apps/example diff --git a/go.work.sum b/go.work.sum index f2e399c3e9b..be369dac104 100644 --- a/go.work.sum +++ b/go.work.sum @@ -275,6 +275,7 @@ github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.9.1/go.mod h1:Ny github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4= github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM= github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo= github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= @@ -335,6 +336,8 @@ github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4Oe github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= +github.com/MissingRoberto/slog-gokit v0.0.0-20251105092822-783f72952ce4 h1:gTtFbl79tuZSeJuSO7kXSbmXSvKSa/PoUXda1tuz0O8= +github.com/MissingRoberto/slog-gokit v0.0.0-20251105092822-783f72952ce4/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= @@ -407,44 +410,75 @@ github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7Rfg github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1 h1:nMp7diZObd4XEVUR0pEvn7/E13JIgManMX79Q6quV6E= github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1/go.mod h1:MVYeeOhILFFemC/XlYTClvBjYZrg/EPd3ts885KrNTI= github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= +github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.5 h1:oUEqVqonG3xuarrsze1KVJ30KagNYDemikTbdu8KlN8= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.5/go.mod h1:VNM08cHlOsIbSHRqb6D/M2L4kKXfJv3A2/f0GNbOQSc= github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.7.87 h1:oDPArGgCrG/4aTi86ij3S2PB59XXkTSKYVNQlmqRHXQ= github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.7.87/go.mod h1:ZeQC4gVarhdcWeM1c90DyBLaBCNhEeAbKUXwVI/byvw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0 h1:A99gjqZDbdhjtjJVZrmVzVKO2+p3MSg35bDWtbMQVxw= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0/go.mod h1:mWB0GE1bqcVSvpW7OtFA0sKuHk52+IqtnsYU2jUfYAs= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.0 h1:0wOCTKrmwkyC8Bk76hYH/B4IJn5MGt6gMkSXc0A2uyc= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.0/go.mod h1:He/RikglWUczbkV+fkdpcV/3GdL/rTRNVy7VaUiezMo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.17 h1:x187MqiHwBGjMGAed8Y8K1VGuCtFvQvXb24r+bwmSdo= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.17/go.mod h1:mC9qMbA6e1pwEq6X3zDGtZRXMG2YaElJkbJlMVHLs5I= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= github.com/aws/aws-sdk-go-v2/service/kinesis v1.33.0 h1:JPXkrQk5OS/+Q81fKH97Ll/Vmmy0p9vwHhxw+V+tVjg= github.com/aws/aws-sdk-go-v2/service/kinesis v1.33.0/go.mod h1:dJngkoVMrq0K7QvRkdRZYM4NUp6cdWa2GBdpm8zoY8U= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2 h1:vlYXbindmagyVA3RS2SPd47eKZ00GZZQcr+etTviHtc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2/go.mod h1:yGhDiLKguA3iFJYxbrQkQiNzuy+ddxesSZYWVeeEH5Q= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.7 h1:d+mnMa4JbJlooSbYQfrJpit/YINaB30JEVgrhtjZneA= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.7/go.mod h1:1X1NotbcGHH7PCQJ98PsExSxsJj/VWzz8MfFz43+02M= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 h1:eSTEdxkfle2G98FE+Xl3db/XAXXVTJPNQo9K/Ar8oAI= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.3/go.mod h1:1dn0delSO3J69THuty5iwP0US2Glt0mx2qBBlI13pvw= +github.com/aws/aws-sdk-go-v2/service/sns v1.34.2 h1:PajtbJ/5bEo6iUAIGMYnK8ljqg2F1h4mMCGh1acjN30= +github.com/aws/aws-sdk-go-v2/service/sns v1.34.2/go.mod h1:PJtxxMdj747j8DeZENRTTYAz/lx/pADn/U0k7YNNiUY= github.com/aws/aws-sdk-go-v2/service/sns v1.34.7 h1:OBuZE9Wt8h2imuRktu+WfjiTGrnYdCIJg8IX92aalHE= github.com/aws/aws-sdk-go-v2/service/sns v1.34.7/go.mod h1:4WYoZAhHt+dWYpoOQUgkUKfuQbE6Gg/hW4oXE0pKS9U= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz7y/WXbN3RgOoVrc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI= +github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3 h1:j5BchjfDoS7K26vPdyJlyxBIIBGDflq3qjjJKBDlbcI= +github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3/go.mod h1:Bar4MrRxeqdn6XIh8JGfiXuFRmyrrsZNTJotxEJmWW0= github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8 h1:80dpSqWMwx2dAm30Ib7J6ucz1ZHfiv5OCRwN/EnCOXQ= github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8/go.mod h1:IzNt/udsXlETCdvBOL0nmyMe2t9cGmXmZgsdoZGYYhI= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 h1:hgSBvRT7JEWx2+vEGI9/Ld5rZtl7M5lu8PqdvOmbRHw= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE= +github.com/aws/aws-sdk-go-v2/service/ssm v1.58.0 h1:zQz6Q5uaC8s9734DV9UDAm2q1TEEfOvEejDBSulOapI= +github.com/aws/aws-sdk-go-v2/service/ssm v1.58.0/go.mod h1:PUWUl5MDiYNQkUHN9Pyd9kgtA/YhbxnSnHP+yQqzrM8= github.com/aws/aws-sdk-go-v2/service/ssm v1.60.1 h1:OwMzNDe5VVTXD4kGmeK/FtqAITiV8Mw4TCa8IyNO0as= github.com/aws/aws-sdk-go-v2/service/ssm v1.60.1/go.mod h1:IyVabkWrs8SNdOEZLyFFcW9bUltV4G6OQS0s6H20PHg= github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/awslabs/aws-lambda-go-api-proxy v0.16.2 h1:CJyGEyO1CIwOnXTU40urf0mchf6t3voxpvUDikOU9LY= github.com/awslabs/aws-lambda-go-api-proxy v0.16.2/go.mod h1:vxxjwBHe/KbgFeNlAP/Tvp4SsVRL3WQamcWRxqVh0z0= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -549,7 +583,6 @@ github.com/couchbase/ghistogram v0.1.0 h1:b95QcQTCzjTUocDXp/uMgSNQi8oj1tGwnJ4bOD github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o= github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -705,6 +738,7 @@ github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1: github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= @@ -804,13 +838,76 @@ github.com/grafana/dskit v0.0.0-20250818234656-8ff9c6532e85/go.mod h1:kImsvJ1xnm github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= github.com/grafana/go-mysql-server v0.20.1-0.20251027172658-317a8d46ffa4/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= +github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= +github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= +github.com/grafana/grafana-app-sdk v0.41.0 h1:SYHN3U7B1myRKY3UZZDkFsue9TDmAOap0UrQVTqtYBU= +github.com/grafana/grafana-app-sdk v0.41.0/go.mod h1:Wg/3vEZfok1hhIWiHaaJm+FwkosfO98o8KbeLFEnZpY= +github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc= +github.com/grafana/grafana-app-sdk v0.47.0/go.mod h1:kywXmkppq0oReUMzkjTW8Fq2EBzyN7v914jttTWnWxA= +github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= +github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= +github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk/logging v0.43.0/go.mod h1:0xrjKSGY5z+NLGuGsXQpxiCHR4Smu79i/CbAfdkaB1M= +github.com/grafana/grafana-app-sdk/logging v0.43.1/go.mod h1:0xrjKSGY5z+NLGuGsXQpxiCHR4Smu79i/CbAfdkaB1M= +github.com/grafana/grafana-app-sdk/logging v0.43.2/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.46.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.48.0 h1:xolkQxBlA2LQF4hprKIAeu+zUem1DigYZ6XC1TOhFJE= github.com/grafana/grafana-app-sdk/logging v0.48.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/plugin v0.41.0 h1:ShUvGpAVzM3UxcsfwS6l/lwW4ytDeTbCQXf8w2P8Yp8= +github.com/grafana/grafana-app-sdk/plugin v0.41.0/go.mod h1:YIhimVfAqtOp3kdhxOanaSZjypVKh/bYxf9wfFfhDm0= +github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= +github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= +github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= +github.com/grafana/grafana-aws-sdk v1.0.2/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8= +github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= +github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= +github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q= +github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI= +github.com/grafana/grafana-plugin-sdk-go v0.275.0/go.mod h1:mO9LJqdXDh5JpO/xIdPAeg5LdThgQ06Y/SLpXDWKw2c= +github.com/grafana/grafana-plugin-sdk-go v0.277.0/go.mod h1:mAUWg68w5+1f5TLDqagIr8sWr1RT9h7ufJl5NMcWJAU= github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= github.com/grafana/grafana-plugin-sdk-go v0.279.0/go.mod h1:/7oGN6Z7DGTGaLHhgIYrRr6Wvmdsb3BLw5hL4Kbjy88= +github.com/grafana/grafana-plugin-sdk-go v0.280.0/go.mod h1:Z15Wiq3c4I0tzHYrLYpOqrO8u3+2RJ+HN2Q9uiZTILA= +github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA= +github.com/grafana/grafana/apps/advisor v0.0.0-20250220154326-6e5de80ef295/go.mod h1:9I1dKV3Dqr0NPR9Af0WJGxOytp5/6W3JLiNChOz8r+c= +github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A= +github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250416173722-ec17e0e4ce03/go.mod h1:oemrhKvFxxc5m32xKHPxInEHAObH0/hPPyHUiBUZ1Cc= +github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250506052906-7a2fc797fb4a/go.mod h1:VkX53kBiqIMHBoGgeEDJnzm5Nwcmv/726tuZuT5SvJY= +github.com/grafana/grafana/apps/alerting/rules v0.0.0-20250731223157-26b18dda3364/go.mod h1:wi4njPm5mJ8IpK13h57be8sWoxOhqr1UQOwmXhRM9Gk= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250616135341-59c2f154336b/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250616145019-8d27f12428cb/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:eR8wca74ADgxBrvX0uNpdB1qnPaGx/KhCm4Xj8oqHfQ= +github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI= +github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805113453-4b17c24d67ff h1:JDT0Mcfpi3c525xzeli+v5dR9pf5HhdFjr8djRdhs10= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805113453-4b17c24d67ff/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805120145-0c5a00302924 h1:uGXX6gCF1q2ytIL0w1X3UAKgF/UZ7eDDAgOaSqLOeW8= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805120145-0c5a00302924/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805123034-066163d71001 h1:y2AHkdji2I+zXv8rsSC8OjWEzJJjqW5OlmCsZR5+RuU= +github.com/grafana/grafana/apps/preferences v0.0.0-20250805123034-066163d71001/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs= +github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA= +github.com/grafana/grafana/pkg/semconv v0.0.0-20250121113133-e747350fee2d/go.mod h1:tfLnBpPYgwrBMRz4EXqPCZJyCjEG4Ev37FSlXnocJ2c= +github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250121113133-e747350fee2d/go.mod h1:CXpwZ3Mkw6xVlGKc0SqUxqXCP3Uv182q6qAQnLaLxRg= +github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:xrKQcxQxz+IUF90ybtfENFeEXtlj9nAsX/3Fw0KEIeQ= +github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed h1:59JF1WhHLT+lNX89Tm1OzOEySMVMASAhaPbsRjtp8Kc= +github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed/go.mod h1:OIAAKNgG5fpuJQRNO1lUSj9nc18Xl3O7M8fjIlBO1cI= +github.com/grafana/nanogit v0.0.0-20250619160700-ebf70d342aa5 h1:MAQ2B0cu0V1S91ZjVa7NomNZFjaR2SmdtvdwhqBtyhU= +github.com/grafana/nanogit v0.0.0-20250619160700-ebf70d342aa5/go.mod h1:tN93IZUaAmnSWgL0IgnKdLv6DNeIhTJGvl1wvQMrWco= +github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3 h1:6D2gGAwyQBElSrp3E+9lSr7k8gLuP3Aiy20rweLWeBw= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3/go.mod h1:YeND+6FDA7OuFgDzYODN8kfPhXLCehcpxe4T9mdnpCY= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975 h1:4/BZkGObFWZf4cLbE2Vqg/1VTz67Q0AJ7LHspWLKJoQ= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975/go.mod h1:FGdGvhI40Dq+CTQaSzK9evuve774cgOUdGfVO04OXkw= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/sqlds/v4 v4.2.4/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU= github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU= @@ -1277,6 +1374,7 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= @@ -1308,6 +1406,7 @@ github.com/tdewolff/minify/v2 v2.12.8 h1:Q2BqOTmlMjoutkuD/OPCnJUpIqrzT3nRPkw+q+K github.com/tdewolff/minify/v2 v2.12.8/go.mod h1:YRgk7CC21LZnbuke2fmYnCTq+zhCgpb0yJACOTUNJ1E= github.com/tdewolff/parse/v2 v2.6.7 h1:WrFllrqmzAcrKHzoYgMupqgUBIfBVOb0yscFzDf8bBg= github.com/tdewolff/parse/v2 v2.6.7/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= +github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= github.com/testcontainers/testcontainers-go/modules/azurite v0.35.0 h1:gUZ25e1DVE/0+ZZ0nupsIo+C1j7UNloN7Pkg3w6tceI= github.com/testcontainers/testcontainers-go/modules/azurite v0.35.0/go.mod h1:2Fc67EpyOEexLAF99zhSuzu9H22zd83pkjxEHHTtHf4= github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 h1:i1Kh9fmXgHG9z3uzJv5Arz7pDKVaaNpLrqyd+0xhYMA= @@ -1326,6 +1425,7 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= @@ -1409,6 +1509,7 @@ github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= @@ -1761,6 +1862,7 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= @@ -1840,6 +1942,7 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= @@ -1854,6 +1957,7 @@ golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c golang.org/x/tools v0.24.1/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= diff --git a/jest.config.codeowner.js b/jest.config.codeowner.js index 5e1a2755949..8e26692a8d4 100644 --- a/jest.config.codeowner.js +++ b/jest.config.codeowner.js @@ -35,17 +35,20 @@ const sourceFiles = teamFiles.filter((file) => { const ext = path.extname(file); return ( ['.ts', '.tsx', '.js', '.jsx'].includes(ext) && - // exclude all tests + // exclude all tests and mocks !path.matchesGlob(file, '**/test/**/*') && !file.includes('.test.') && !file.includes('.spec.') && + !path.matchesGlob(file, '**/__mocks__/**/*') && // and storybook stories !file.includes('.story.') && // and generated files !file.includes('.gen.ts') && // and type definitions !file.includes('.d.ts') && - !file.endsWith('/types.ts') + !file.endsWith('/types.ts') && + // and anything in graveyard + !path.matchesGlob(file, '**/graveyard/**/*') ); }); diff --git a/lerna.json b/lerna.json index 70b729cb019..d6dc835d802 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "12.3.0-pre" + "version": "12.4.0-pre" } diff --git a/package.json b/package.json index 5d7dd234e25..0402c332903 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "repository": "github:grafana/grafana", "scripts": { "check-frontend-dev": "./scripts/check-frontend-dev.sh", @@ -92,7 +92,7 @@ "@emotion/eslint-plugin": "11.12.0", "@grafana/eslint-config": "8.2.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", - "@grafana/plugin-e2e": "2.1.7", + "@grafana/plugin-e2e": "^3.0.1", "@grafana/test-utils": "workspace:*", "@manypkg/get-packages": "^3.0.0", "@npmcli/package-json": "^6.0.0", @@ -278,7 +278,7 @@ "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", "@grafana/api-clients": "workspace:*", - "@grafana/assistant": "0.1.0", + "@grafana/assistant": "0.1.4", "@grafana/aws-sdk": "0.7.1", "@grafana/azure-sdk": "0.0.8", "@grafana/data": "workspace:*", @@ -296,8 +296,8 @@ "@grafana/plugin-ui": "^0.10.10", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.42.1", - "@grafana/scenes-react": "^6.42.1", + "@grafana/scenes": "^6.46.0", + "@grafana/scenes-react": "^6.46.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", @@ -306,7 +306,7 @@ "@leeoniya/ufuzzy": "1.0.19", "@lezer/common": "1.2.3", "@lezer/highlight": "1.2.1", - "@lezer/lr": "1.4.2", + "@lezer/lr": "1.4.3", "@locker/near-membrane-dom": "0.14.0", "@locker/near-membrane-shared": "0.14.0", "@locker/near-membrane-shared-dom": "0.14.0", @@ -382,7 +382,7 @@ "nanoid": "^5.0.9", "node-forge": "^1.3.1", "ol": "10.6.1", - "ol-ext": "4.0.35", + "ol-ext": "4.0.36", "ol-mapbox-style": "^13.0.1", "pluralize": "^8.0.0", "prismjs": "1.30.0", diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json index 226771138ff..da13aa6fc0f 100644 --- a/packages/grafana-alerting/package.json +++ b/packages/grafana-alerting/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/alerting", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Alerting Library – Build vertical integrations on top of the industry-leading alerting solution", "keywords": [ "typescript", @@ -94,7 +94,7 @@ "dependencies": { "@emotion/css": "11.13.5", "@faker-js/faker": "^9.8.0", - "@grafana/i18n": "12.3.0-pre", + "@grafana/i18n": "12.4.0-pre", "@reduxjs/toolkit": "^2.9.0", "fishery": "^2.3.1", "lodash": "^4.17.21", diff --git a/packages/grafana-api-clients/package.json b/packages/grafana-api-clients/package.json index 9741c97303b..79809210045 100644 --- a/packages/grafana-api-clients/package.json +++ b/packages/grafana-api-clients/package.json @@ -3,7 +3,7 @@ "license": "Apache-2.0", "private": true, "name": "@grafana/api-clients", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana API client utilities", "keywords": [ "grafana", @@ -76,9 +76,9 @@ "import": "./src/clients/rtkq/provisioning/v0alpha1/index.ts", "require": "./src/clients/rtkq/provisioning/v0alpha1/index.ts" }, - "./rtkq/shorturl/v1alpha1": { - "import": "./src/clients/rtkq/shorturl/v1alpha1/index.ts", - "require": "./src/clients/rtkq/shorturl/v1alpha1/index.ts" + "./rtkq/shorturl/v1beta1": { + "import": "./src/clients/rtkq/shorturl/v1beta1/index.ts", + "require": "./src/clients/rtkq/shorturl/v1beta1/index.ts" } }, "publishConfig": { diff --git a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts index 4b4de61be87..3b0fb2a5134 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/advisor.grafana.app/v0alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listCheck: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts index 6cfab61cdba..7fa1043fd5c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/correlations.grafana.app/v0alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listCorrelation: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts index 6ca3505d067..26f8053d29c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/dashboard.grafana.app/v0alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listDashboard: build.query({ @@ -244,6 +244,7 @@ const injectedRtkApi = api folder: queryArg.folder, facet: queryArg.facet, tags: queryArg.tags, + libraryPanel: queryArg.libraryPanel, sort: queryArg.sort, limit: queryArg.limit, explain: queryArg.explain, @@ -608,6 +609,8 @@ export type GetSearchApiArg = { facet?: string[]; /** tag query filter */ tags?: string[]; + /** find dashboards that reference a given libraryPanel */ + libraryPanel?: string; /** sortable field */ sort?: string; /** number of results to return */ @@ -845,6 +848,7 @@ export type DashboardAccess = { /** The permissions part */ canSave: boolean; canStar: boolean; + isPublic: boolean; /** Metadata fields */ slug?: string; url?: string; diff --git a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts index 9ef9e4ebfab..984d95dcd68 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/folder.grafana.app/v1beta1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listFolder: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts index 3645b4b3373..851680b4be4 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts @@ -2,6 +2,7 @@ import { api } from './baseAPI'; export const addTagTypes = [ 'API Discovery', 'Display', + 'ExternalGroupMapping', 'ServiceAccount', 'SSOSetting', 'TeamBinding', @@ -15,7 +16,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/iam.grafana.app/v0alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), getDisplayMapping: build.query({ @@ -27,6 +28,130 @@ const injectedRtkApi = api }), providesTags: ['Display'], }), + listExternalGroupMapping: build.query({ + query: (queryArg) => ({ + url: `/externalgroupmappings`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['ExternalGroupMapping'], + }), + createExternalGroupMapping: build.mutation< + CreateExternalGroupMappingApiResponse, + CreateExternalGroupMappingApiArg + >({ + query: (queryArg) => ({ + url: `/externalgroupmappings`, + method: 'POST', + body: queryArg.externalGroupMapping, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['ExternalGroupMapping'], + }), + deletecollectionExternalGroupMapping: build.mutation< + DeletecollectionExternalGroupMappingApiResponse, + DeletecollectionExternalGroupMappingApiArg + >({ + query: (queryArg) => ({ + url: `/externalgroupmappings`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['ExternalGroupMapping'], + }), + getExternalGroupMapping: build.query({ + query: (queryArg) => ({ + url: `/externalgroupmappings/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['ExternalGroupMapping'], + }), + replaceExternalGroupMapping: build.mutation< + ReplaceExternalGroupMappingApiResponse, + ReplaceExternalGroupMappingApiArg + >({ + query: (queryArg) => ({ + url: `/externalgroupmappings/${queryArg.name}`, + method: 'PUT', + body: queryArg.externalGroupMapping, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['ExternalGroupMapping'], + }), + deleteExternalGroupMapping: build.mutation< + DeleteExternalGroupMappingApiResponse, + DeleteExternalGroupMappingApiArg + >({ + query: (queryArg) => ({ + url: `/externalgroupmappings/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['ExternalGroupMapping'], + }), + updateExternalGroupMapping: build.mutation< + UpdateExternalGroupMappingApiResponse, + UpdateExternalGroupMappingApiArg + >({ + query: (queryArg) => ({ + url: `/externalgroupmappings/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['ExternalGroupMapping'], + }), listServiceAccount: build.query({ query: (queryArg) => ({ url: `/serviceaccounts`, @@ -564,6 +689,175 @@ export type GetDisplayMappingApiArg = { /** Display keys */ key: string[]; }; +export type ListExternalGroupMappingApiResponse = /** status 200 OK */ ExternalGroupMappingList; +export type ListExternalGroupMappingApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateExternalGroupMappingApiResponse = /** status 200 OK */ + | ExternalGroupMapping + | /** status 201 Created */ ExternalGroupMapping + | /** status 202 Accepted */ ExternalGroupMapping; +export type CreateExternalGroupMappingApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + externalGroupMapping: ExternalGroupMapping; +}; +export type DeletecollectionExternalGroupMappingApiResponse = /** status 200 OK */ Status; +export type DeletecollectionExternalGroupMappingApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetExternalGroupMappingApiResponse = /** status 200 OK */ ExternalGroupMapping; +export type GetExternalGroupMappingApiArg = { + /** name of the ExternalGroupMapping */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceExternalGroupMappingApiResponse = /** status 200 OK */ + | ExternalGroupMapping + | /** status 201 Created */ ExternalGroupMapping; +export type ReplaceExternalGroupMappingApiArg = { + /** name of the ExternalGroupMapping */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + externalGroupMapping: ExternalGroupMapping; +}; +export type DeleteExternalGroupMappingApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteExternalGroupMappingApiArg = { + /** name of the ExternalGroupMapping */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateExternalGroupMappingApiResponse = /** status 200 OK */ + | ExternalGroupMapping + | /** status 201 Created */ ExternalGroupMapping; +export type UpdateExternalGroupMappingApiArg = { + /** name of the ExternalGroupMapping */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + patch: Patch; +}; export type ListServiceAccountApiResponse = /** status 200 OK */ ServiceAccountList; export type ListServiceAccountApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -1494,25 +1788,27 @@ export type ObjectMeta = { Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ uid?: string; }; -export type ServiceAccountSpec = { - disabled: boolean; - plugin: string; - role: string; - title: string; +export type ExternalGroupMappingTeamRef = { + /** Name is the unique identifier for a team. */ + name: string; }; -export type ServiceAccount = { +export type ExternalGroupMappingSpec = { + externalGroupId: string; + teamRef: ExternalGroupMappingTeamRef; +}; +export type ExternalGroupMapping = { /** 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 */ apiVersion?: string; /** 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 */ kind?: string; metadata: ObjectMeta; - /** Spec is the spec of the ServiceAccount */ - spec: ServiceAccountSpec; + /** Spec is the spec of the ExternalGroupMapping */ + spec: ExternalGroupMappingSpec; }; -export type ServiceAccountList = { +export type ExternalGroupMappingList = { /** 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 */ apiVersion?: string; - items: ServiceAccount[]; + items: ExternalGroupMapping[]; /** 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 */ kind?: string; metadata: ListMeta; @@ -1562,6 +1858,29 @@ export type Status = { status?: string; }; export type Patch = object; +export type ServiceAccountSpec = { + disabled: boolean; + plugin: string; + role: string; + title: string; +}; +export type ServiceAccount = { + /** 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 */ + apiVersion?: string; + /** 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 */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the ServiceAccount */ + spec: ServiceAccountSpec; +}; +export type ServiceAccountList = { + /** 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 */ + apiVersion?: string; + items: ServiceAccount[]; + /** 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 */ + kind?: string; + metadata: ListMeta; +}; export type ServiceAccountToken = { created: Time; expires?: Time; @@ -1736,6 +2055,15 @@ export const { useLazyGetApiResourcesQuery, useGetDisplayMappingQuery, useLazyGetDisplayMappingQuery, + useListExternalGroupMappingQuery, + useLazyListExternalGroupMappingQuery, + useCreateExternalGroupMappingMutation, + useDeletecollectionExternalGroupMappingMutation, + useGetExternalGroupMappingQuery, + useLazyGetExternalGroupMappingQuery, + useReplaceExternalGroupMappingMutation, + useDeleteExternalGroupMappingMutation, + useUpdateExternalGroupMappingMutation, useListServiceAccountQuery, useLazyListServiceAccountQuery, useCreateServiceAccountMutation, diff --git a/packages/grafana-api-clients/src/clients/rtkq/index.ts b/packages/grafana-api-clients/src/clients/rtkq/index.ts index 77c02e78170..4bdede94ed9 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/index.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/index.ts @@ -11,7 +11,7 @@ import { generatedAPI as playlistAPIv0alpha1 } from './playlist/v0alpha1'; import { generatedAPI as preferencesUserAPI } from './preferences/user'; import { generatedAPI as preferencesAPIv1alpha1 } from './preferences/v1alpha1'; import { generatedAPI as provisioningAPIv0alpha1 } from './provisioning/v0alpha1'; -import { generatedAPI as shortURLAPIv1alpha1 } from './shorturl/v1alpha1'; +import { generatedAPI as shortURLAPIv1beta1 } from './shorturl/v1beta1'; import { generatedAPI as legacyUserAPI } from './user'; // PLOP_INJECT_IMPORT @@ -26,7 +26,7 @@ export const allMiddleware = [ preferencesAPIv1alpha1.middleware, preferencesUserAPI.middleware, provisioningAPIv0alpha1.middleware, - shortURLAPIv1alpha1.middleware, + shortURLAPIv1beta1.middleware, correlationsAPIv0alpha1.middleware, legacyUserAPI.middleware, // PLOP_INJECT_MIDDLEWARE @@ -43,7 +43,7 @@ export const allReducers = { [preferencesAPIv1alpha1.reducerPath]: preferencesAPIv1alpha1.reducer, [preferencesUserAPI.reducerPath]: preferencesUserAPI.reducer, [provisioningAPIv0alpha1.reducerPath]: provisioningAPIv0alpha1.reducer, - [shortURLAPIv1alpha1.reducerPath]: shortURLAPIv1alpha1.reducer, + [shortURLAPIv1beta1.reducerPath]: shortURLAPIv1beta1.reducer, [correlationsAPIv0alpha1.reducerPath]: correlationsAPIv0alpha1.reducer, [legacyUserAPI.reducerPath]: legacyUserAPI.reducer, // PLOP_INJECT_REDUCER diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts index ce5c5cae8c7..058aa2b057c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -108,13 +108,13 @@ const injectedRtkApi = api query: () => ({ url: `/access-control/status` }), providesTags: ['access_control', 'enterprise'], }), - listTeamsRoles: build.mutation({ + listTeamsRoles: build.query({ query: (queryArg) => ({ url: `/access-control/teams/roles/search`, method: 'POST', body: queryArg.rolesSearchQuery, }), - invalidatesTags: ['access_control', 'enterprise'], + providesTags: ['access_control', 'enterprise'], }), listTeamRoles: build.query({ query: (queryArg) => ({ url: `/access-control/teams/${queryArg.teamId}/roles` }), @@ -129,7 +129,11 @@ const injectedRtkApi = api invalidatesTags: ['access_control', 'enterprise'], }), setTeamRoles: build.mutation({ - query: (queryArg) => ({ url: `/access-control/teams/${queryArg.teamId}/roles`, method: 'PUT' }), + query: (queryArg) => ({ + url: `/access-control/teams/${queryArg.teamId}/roles`, + method: 'PUT', + body: queryArg.setTeamRolesCommand, + }), invalidatesTags: ['access_control', 'enterprise'], }), removeTeamRole: build.mutation({ @@ -1599,6 +1603,8 @@ const injectedRtkApi = api perpage: queryArg.perpage, name: queryArg.name, query: queryArg.query, + accesscontrol: queryArg.accesscontrol, + sort: queryArg.sort, }, }), providesTags: ['teams'], @@ -2142,6 +2148,7 @@ export type SetTeamRolesApiResponse = /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; export type SetTeamRolesApiArg = { teamId: number; + setTeamRolesCommand: SetTeamRolesCommand; }; export type RemoveTeamRoleApiResponse = /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; @@ -3445,6 +3452,8 @@ export type SearchTeamsApiArg = { name?: string; /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ query?: string; + accesscontrol?: boolean; + sort?: string; }; export type RemoveTeamGroupApiQueryApiResponse = /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; @@ -3880,26 +3889,26 @@ export type ErrorResponseBody = { For example, a 412 Precondition Failed error may include additional information of why that error happened. */ status?: string; }; -export type PermissionIsTheModelForAccessControlPermissions = { +export type Permission = { action?: string; created?: string; scope?: string; updated?: string; }; export type RoleDto = { - created?: string; + created: string; delegatable?: boolean; - description?: string; - displayName?: string; + description: string; + displayName: string; global?: boolean; - group?: string; + group: string; hidden?: boolean; mapped?: boolean; - name?: string; - permissions?: PermissionIsTheModelForAccessControlPermissions[]; - uid?: string; - updated?: string; - version?: number; + name: string; + permissions?: Permission[]; + uid: string; + updated: string; + version: number; }; export type CreateRoleForm = { description?: string; @@ -3908,7 +3917,7 @@ export type CreateRoleForm = { group?: string; hidden?: boolean; name?: string; - permissions?: PermissionIsTheModelForAccessControlPermissions[]; + permissions?: Permission[]; uid?: string; version?: number; }; @@ -3922,7 +3931,7 @@ export type UpdateRoleCommand = { group: string; hidden?: boolean; name?: string; - permissions?: PermissionIsTheModelForAccessControlPermissions[]; + permissions?: Permission[]; version?: number; }; export type RoleAssignmentsDto = { @@ -3946,6 +3955,10 @@ export type RolesSearchQuery = { export type AddTeamRoleCommand = { roleUid?: string; }; +export type SetTeamRolesCommand = { + includeHidden?: boolean; + roleUids?: string[]; +}; export type AddUserRoleCommand = { global?: boolean; roleUid?: string; @@ -6019,7 +6032,7 @@ export type CreateDashboardSnapshotCommand = { }; export type CreateTeamCommand = { email?: string; - name?: string; + name: string; }; export type TeamDto = { accessControl?: { @@ -6028,13 +6041,14 @@ export type TeamDto = { avatarUrl?: string; email?: string; externalUID?: string; - id?: number; - isProvisioned?: boolean; - memberCount?: number; - name?: string; - orgId?: number; + /** @deprecated Use UID instead */ + id: number; + isProvisioned: boolean; + memberCount: number; + name: string; + orgId: number; permission?: PermissionType; - uid?: string; + uid: string; }; export type SearchTeamQueryResult = { page?: number; @@ -6078,7 +6092,7 @@ export type TeamMemberDto = { userUID?: string; }; export type AddTeamMemberCommand = { - userId?: number; + userId: number; }; export type SetTeamMembershipsCommand = { admins?: string[]; @@ -6515,7 +6529,8 @@ export const { useSetRoleAssignmentsMutation, useGetAccessControlStatusQuery, useLazyGetAccessControlStatusQuery, - useListTeamsRolesMutation, + useListTeamsRolesQuery, + useLazyListTeamsRolesQuery, useListTeamRolesQuery, useLazyListTeamRolesQuery, useAddTeamRoleMutation, diff --git a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts index 8e1bdfc1c4c..3c7edccaf9c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/playlist.grafana.app/v0alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listPlaylist: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts index 0edda5148ea..c90f694defb 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/preferences.grafana.app/v1alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listPreferences: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index a3c5d30952a..67bdca12d27 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/provisioning.grafana.app/v0alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listJob: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/baseAPI.ts b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/baseAPI.ts similarity index 82% rename from packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/baseAPI.ts rename to packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/baseAPI.ts index 4d1f28bd5d8..c5e91c9f452 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/baseAPI.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/baseAPI.ts @@ -4,11 +4,11 @@ import { getAPIBaseURL } from '../../../../utils/utils'; import { createBaseQuery } from '../../createBaseQuery'; export const API_GROUP = 'shorturl.grafana.app' as const; -export const API_VERSION = 'v1alpha1' as const; +export const API_VERSION = 'v1beta1' as const; export const BASE_URL = getAPIBaseURL(API_GROUP, API_VERSION); export const api = createApi({ - reducerPath: 'shortURLAPIv1alpha1', + reducerPath: 'shortURLAPIv1beta1', baseQuery: createBaseQuery({ baseURL: BASE_URL, }), diff --git a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/endpoints.gen.ts similarity index 99% rename from packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts rename to packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/endpoints.gen.ts index a4bfc1df1ba..1305d03dcc6 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/endpoints.gen.ts @@ -7,7 +7,7 @@ const injectedRtkApi = api .injectEndpoints({ endpoints: (build) => ({ getApiResources: build.query({ - query: () => ({ url: `/apis/shorturl.grafana.app/v1alpha1/` }), + query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), listShortUrl: build.query({ diff --git a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/index.ts b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/index.ts similarity index 100% rename from packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/index.ts rename to packages/grafana-api-clients/src/clients/rtkq/shorturl/v1beta1/index.ts diff --git a/packages/grafana-api-clients/src/generator/plopfile.ts b/packages/grafana-api-clients/src/generator/plopfile.ts index ed3953bfdc7..3f40c4adc42 100644 --- a/packages/grafana-api-clients/src/generator/plopfile.ts +++ b/packages/grafana-api-clients/src/generator/plopfile.ts @@ -107,15 +107,20 @@ export default function plopGenerator(plop: NodePlopAPI) { return actions; }; - // Read files from data/openapi directory and use as an array of API clients that the user can select + const getOpenAPISpecs = (isEnterprise: boolean): string[] => { + const openapiDir = isEnterprise + ? path.join(basePath, 'pkg/extensions/apiserver/tests/openapi_snapshots') + : path.join(basePath, 'pkg/tests/apis/openapi_snapshots'); - const openapiDir = path.join(basePath, 'data/openapi'); - let possibleOpenAPISpecs: string[] = []; - try { - possibleOpenAPISpecs = fs.readdirSync(openapiDir).filter((file: string) => file.endsWith('.json')); - } catch (e) { - possibleOpenAPISpecs = []; - } + try { + const files = fs.readdirSync(openapiDir).filter((file: string) => file.endsWith('.json')); + return files; + } catch (e) { + throw new Error( + "No OpenAPI specs found! Are you trying to generate an API client for enterprise but haven't linked your local environment?" + ); + } + }; const generator: PlopGeneratorConfig = { description: 'Generate RTK Query API client for a Grafana API group', @@ -129,7 +134,9 @@ export default function plopGenerator(plop: NodePlopAPI) { { type: 'list', loop: false, - choices: possibleOpenAPISpecs, + choices: (answers: { isEnterprise?: boolean }) => { + return getOpenAPISpecs(answers.isEnterprise ?? false); + }, pageSize: 50, name: 'apiInfo', message: 'OpenAPI spec:', diff --git a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts index a9422a02815..8b211fbca8c 100644 --- a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts +++ b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts @@ -53,6 +53,12 @@ const config: ConfigFile = { tag: true, apiFile: '../clients/rtkq/legacy/baseAPI.ts', filterEndpoints: (_name, operation) => !operation.operation.deprecated, + endpointOverrides: [ + { + pattern: 'listTeamsRoles', + type: 'query', + }, + ], }, '../clients/rtkq/migrate-to-cloud/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), @@ -100,7 +106,9 @@ const config: ConfigFile = { ...createAPIConfig('playlist', 'v0alpha1'), ...createAPIConfig('preferences', 'v1alpha1'), ...createAPIConfig('provisioning', 'v0alpha1'), - ...createAPIConfig('shorturl', 'v1alpha1'), + ...createAPIConfig('shorturl', 'v1beta1'), + ...createAPIConfig('shorturl', 'v1beta1'), + ...createAPIConfig('shorturl', 'v1beta1'), // PLOP_INJECT_API_CLIENT - Used by the API client generator }, }; diff --git a/packages/grafana-api-clients/src/scripts/process-specs.ts b/packages/grafana-api-clients/src/scripts/process-specs.ts index f3de40bf6ed..5503b24408a 100644 --- a/packages/grafana-api-clients/src/scripts/process-specs.ts +++ b/packages/grafana-api-clients/src/scripts/process-specs.ts @@ -23,7 +23,7 @@ function processOpenAPISpec(spec: OpenAPIV3.Document) { continue; } // Remove the specified part from the path key - const newPathKey = path.replace(/^\/apis\/[^\/]+\/[^\/]+\/namespaces\/\{namespace}/, ''); + const newPathKey = path.replace(/^\/apis\/[^\/]+\/[^\/]+/, '').replace(/^\/namespaces\/\{namespace}/, ''); // Process each method in the path (e.g., get, post) const newPathItem: Record = {}; diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index c990c304caf..2fce041444e 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -56,8 +56,8 @@ }, "dependencies": { "@braintree/sanitize-url": "7.0.1", - "@grafana/i18n": "12.3.0-pre", - "@grafana/schema": "12.3.0-pre", + "@grafana/i18n": "12.4.0-pre", + "@grafana/schema": "12.4.0-pre", "@leeoniya/ufuzzy": "1.0.19", "@types/d3-interpolate": "^3.0.0", "@types/string-hash": "1.1.3", diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts index 8817812b5bd..fdcfed8c039 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts @@ -8,14 +8,14 @@ import { guessFieldTypeForField } from './processDataFrame'; * * @deprecated use arrayToDataFrame */ -export class ArrayDataFrame implements DataFrame { +export class ArrayDataFrame implements DataFrame { fields: Field[] = []; length = 0; name?: string; refId?: string; meta?: QueryResultMeta; - constructor(source: T[], names?: string[]) { + constructor(source: unknown[], names?: string[]) { return arrayToDataFrame(source, names); // returns a standard DataFrame } } @@ -27,7 +27,7 @@ export class ArrayDataFrame implements DataFrame { * * @public */ -export function arrayToDataFrame(source: Array> | unknown[], names?: string[]): DataFrame { +export function arrayToDataFrame(source: unknown[], names?: string[]): DataFrame { const df: DataFrame = { fields: [], length: source.length, diff --git a/packages/grafana-data/src/dataframe/DataFrameView.ts b/packages/grafana-data/src/dataframe/DataFrameView.ts index d4d3c157d62..e911748fa39 100644 --- a/packages/grafana-data/src/dataframe/DataFrameView.ts +++ b/packages/grafana-data/src/dataframe/DataFrameView.ts @@ -23,7 +23,7 @@ export class DataFrameView extends FunctionalVector { constructor(private data: DataFrame) { super(); const obj = {} as T; - const fields = {} as any; + const fields: any = {}; for (let i = 0; i < data.fields.length; i++) { const field = data.fields[i]; diff --git a/packages/grafana-data/src/dataframe/MutableDataFrame.ts b/packages/grafana-data/src/dataframe/MutableDataFrame.ts index 4437aa44b5e..6ac9b79e38a 100644 --- a/packages/grafana-data/src/dataframe/MutableDataFrame.ts +++ b/packages/grafana-data/src/dataframe/MutableDataFrame.ts @@ -13,6 +13,8 @@ export type MutableField = Field; /** @deprecated */ type MutableVectorCreator = (buffer?: unknown[]) => unknown[]; +type Parser = (v: string) => unknown; + export const MISSING_VALUE = undefined; // Treated as connected in new graph panel /** @@ -149,14 +151,14 @@ export class MutableDataFrame extends FunctionalVector implements Da } } - private parsers: Map any> | undefined = undefined; + private parsers: Map | undefined = undefined; /** * @deprecated unclear if this is actually used */ - setParser(field: Field, parser: (v: string) => any) { + setParser(field: Field, parser: Parser) { if (!this.parsers) { - this.parsers = new Map any>(); + this.parsers = new Map(); } this.parsers.set(field, parser); return parser; @@ -222,7 +224,7 @@ export class MutableDataFrame extends FunctionalVector implements Da */ add(value: T): void { // Will add one value for every field - const obj = value as any; + const obj: any = value; for (const field of this.fields) { let val = obj[field.name]; @@ -243,7 +245,7 @@ export class MutableDataFrame extends FunctionalVector implements Da throw new Error('Unable to set value beyond current length'); } - const obj = (value as Record) || {}; + const obj: Record = value || {}; for (const field of this.fields) { field.values[index] = obj[field.name]; } diff --git a/packages/grafana-data/src/dataframe/StreamingDataFrame.ts b/packages/grafana-data/src/dataframe/StreamingDataFrame.ts index bb410406ed9..8d37543f73e 100644 --- a/packages/grafana-data/src/dataframe/StreamingDataFrame.ts +++ b/packages/grafana-data/src/dataframe/StreamingDataFrame.ts @@ -420,9 +420,7 @@ export class StreamingDataFrame implements DataFrame { }; getMatchingFieldIndexes = (fieldPredicate: (f: Field) => boolean): number[] => - this.fields - .map((f, index) => (fieldPredicate(f) ? index : undefined)) - .filter((val) => val !== undefined) as number[]; + this.fields.map((f, index) => (fieldPredicate(f) ? index : undefined)).filter((val) => val !== undefined); getValuesFromLastPacket = (): unknown[][] => this.fields.map((f) => { diff --git a/packages/grafana-data/src/dataframe/processDataFrame.test.ts b/packages/grafana-data/src/dataframe/processDataFrame.test.ts index 5c7593e91c0..bb9ffefefcd 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.test.ts @@ -348,11 +348,11 @@ describe('SeriesData backwards compatibility', () => { expect(isDataFrame(timeseries)).toBeFalsy(); expect(isDataFrame(series)).toBeTruthy(); - const roundtrip = toLegacyResponseData(series) as any; + const roundtrip = toLegacyResponseData(series); expect(isDataFrame(roundtrip)).toBeFalsy(); - expect(roundtrip.type).toBe('docs'); - expect(roundtrip.target).toBe('docs'); - expect(roundtrip.filterable).toBeTruthy(); + expect('type' in roundtrip && roundtrip.type).toBe('docs'); + expect('target' in roundtrip && roundtrip.target).toBe('docs'); + expect('filterable' in roundtrip && roundtrip.filterable).toBeTruthy(); }); }); diff --git a/packages/grafana-data/src/dataframe/processDataFrame.ts b/packages/grafana-data/src/dataframe/processDataFrame.ts index 14621547725..c3fd65486c7 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.ts @@ -26,13 +26,13 @@ import { dataFrameFromJSON } from './DataFrameJSON'; function convertTableToDataFrame(table: TableData): DataFrame { const fields = table.columns.map((c) => { // TODO: should be Column but type does not exists there so not sure whats up here. - const { text, type, ...disp } = c as any; + const { text, type, ...disp } = c as Column & { type?: FieldType }; const values: unknown[] = []; return { - name: text?.length ? text : c, // rename 'text' to the 'name' field - config: (disp || {}) as FieldConfig, + name: text ?? c, // rename 'text' to the 'name' field + config: disp || {}, values, - type: type && Object.values(FieldType).includes(type as FieldType) ? (type as FieldType) : FieldType.other, + type: type && Object.values(FieldType).includes(type) ? type : FieldType.other, }; }); @@ -400,8 +400,9 @@ export const toLegacyResponseData = (frame: DataFrame): TimeSeries | TableData = if (config) { // keep unit etc const { ...column } = config; - (column as Column).text = name; - return column as Column; + const result = column as Column; + result.text = name; + return result; } return { text: name }; }), diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index e058612a3c7..06b7f19444e 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -435,6 +435,7 @@ export { isStandardFieldProp, type OptionDefaults, } from './panel/getPanelOptionsWithDefaults'; +export { type PanelDataSummary, getPanelDataSummary } from './panel/suggestions/getPanelDataSummary'; export { createFieldConfigRegistry } from './panel/registryFactories'; export { type QueryRunner, type QueryRunnerOptions } from './types/queryRunner'; export { type GroupingToMatrixTransformerOptions } from './transformations/transformers/groupingToMatrix'; @@ -650,13 +651,7 @@ export { type PanelMenuItem, type AngularPanelMenuItem, type PanelPluginDataSupport, - type VisualizationSuggestion, - type PanelDataSummary, - type VisualizationSuggestionsSupplier, VizOrientation, - VisualizationSuggestionScore, - VisualizationSuggestionsBuilder, - VisualizationSuggestionsListAppender, } from './types/panel'; export { type DataSourcePluginOptionsEditorProps, @@ -717,6 +712,13 @@ export { type ApplyFieldOverrideOptions, FieldConfigProperty, } from './types/fieldOverrides'; +export { + type VisualizationSuggestion, + type VisualizationSuggestionsSupplier, + VisualizationSuggestionScore, + VisualizationSuggestionsBuilder, + VisualizationSuggestionsListAppender, +} from './types/suggestions'; export { type MatcherConfig, type DataTransformContext, diff --git a/packages/grafana-data/src/panel/PanelPlugin.ts b/packages/grafana-data/src/panel/PanelPlugin.ts index 61985074be9..e9119a37b52 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.ts +++ b/packages/grafana-data/src/panel/PanelPlugin.ts @@ -7,7 +7,6 @@ import { PanelModel } from '../types/dashboard'; import { FieldConfigProperty, FieldConfigSource } from '../types/fieldOverrides'; import { PanelPluginMeta, - VisualizationSuggestionsSupplier, PanelProps, PanelEditorProps, PanelMigrationHandler, @@ -15,6 +14,7 @@ import { PanelPluginDataSupport, } from '../types/panel'; import { GrafanaPlugin } from '../types/plugin'; +import { VisualizationSuggestionsSupplier } from '../types/suggestions'; import { FieldConfigEditorBuilder, PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders'; import { deprecationWarning } from '../utils/deprecationWarning'; @@ -122,11 +122,6 @@ export class PanelPlugin< alertStates: false, }; - /** - * Legacy angular ctrl. If this exists it will be used instead of the panel - */ - angularPanelCtrl?: any; - constructor(panel: ComponentType> | null) { super(); this.panel = panel; diff --git a/packages/grafana-data/src/panel/suggestions/getPanelDataSummary.test.ts b/packages/grafana-data/src/panel/suggestions/getPanelDataSummary.test.ts new file mode 100644 index 00000000000..bef7a302757 --- /dev/null +++ b/packages/grafana-data/src/panel/suggestions/getPanelDataSummary.test.ts @@ -0,0 +1,94 @@ +import { createDataFrame } from '../../dataframe/processDataFrame'; +import { FieldType } from '../../types/dataFrame'; + +import { getPanelDataSummary } from './getPanelDataSummary'; + +describe('getPanelDataSummary', () => { + describe('when called with no dataframes', () => { + it('should return summary with zero counts', () => { + const summary = getPanelDataSummary(); + + expect(summary.rowCountTotal).toBe(0); + expect(summary.rowCountMax).toBe(0); + expect(summary.fieldCount).toBe(0); + expect(summary.frameCount).toBe(0); + expect(summary.hasData).toBe(false); + + expect(summary.fieldCountByType(FieldType.time)).toBe(0); + expect(summary.fieldCountByType(FieldType.number)).toBe(0); + expect(summary.fieldCountByType(FieldType.string)).toBe(0); + expect(summary.fieldCountByType(FieldType.boolean)).toBe(0); + + expect(summary.hasFieldType(FieldType.time)).toBe(false); + expect(summary.hasFieldType(FieldType.number)).toBe(false); + expect(summary.hasFieldType(FieldType.string)).toBe(false); + expect(summary.hasFieldType(FieldType.boolean)).toBe(false); + }); + }); + + describe('when called with a single dataframes', () => { + it('should return correct summary', () => { + const frames = [ + createDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1, 2, 3] }, + { name: 'value', type: FieldType.number, values: [10, 20, 30] }, + ], + }), + ]; + const summary = getPanelDataSummary(frames); + + expect(summary.rowCountTotal).toBe(3); + expect(summary.rowCountMax).toBe(3); + expect(summary.fieldCount).toBe(2); + expect(summary.frameCount).toBe(1); + expect(summary.hasData).toBe(true); + + expect(summary.fieldCountByType(FieldType.time)).toBe(1); + expect(summary.fieldCountByType(FieldType.number)).toBe(1); + expect(summary.fieldCountByType(FieldType.string)).toBe(0); + expect(summary.fieldCountByType(FieldType.boolean)).toBe(0); + + expect(summary.hasFieldType(FieldType.time)).toBe(true); + expect(summary.hasFieldType(FieldType.number)).toBe(true); + expect(summary.hasFieldType(FieldType.string)).toBe(false); + expect(summary.hasFieldType(FieldType.boolean)).toBe(false); + }); + }); + + describe('when called with multiple dataframes', () => { + it('should return correct summary', () => { + const frames = [ + createDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1, 2, 3] }, + { name: 'value', type: FieldType.number, values: [10, 20, 30] }, + ], + }), + createDataFrame({ + fields: [ + { name: 'category', type: FieldType.string, values: ['A', 'B'] }, + { name: 'amount', type: FieldType.number, values: [100, 200] }, + ], + }), + ]; + const summary = getPanelDataSummary(frames); + + expect(summary.rowCountTotal).toBe(5); + expect(summary.rowCountMax).toBe(3); + expect(summary.fieldCount).toBe(4); + expect(summary.frameCount).toBe(2); + expect(summary.hasData).toBe(true); + + expect(summary.fieldCountByType(FieldType.time)).toBe(1); + expect(summary.fieldCountByType(FieldType.number)).toBe(2); + expect(summary.fieldCountByType(FieldType.string)).toBe(1); + expect(summary.fieldCountByType(FieldType.boolean)).toBe(0); + + expect(summary.hasFieldType(FieldType.time)).toBe(true); + expect(summary.hasFieldType(FieldType.number)).toBe(true); + expect(summary.hasFieldType(FieldType.string)).toBe(true); + expect(summary.hasFieldType(FieldType.boolean)).toBe(false); + }); + }); +}); diff --git a/packages/grafana-data/src/panel/suggestions/getPanelDataSummary.ts b/packages/grafana-data/src/panel/suggestions/getPanelDataSummary.ts new file mode 100644 index 00000000000..ea97e1705d7 --- /dev/null +++ b/packages/grafana-data/src/panel/suggestions/getPanelDataSummary.ts @@ -0,0 +1,82 @@ +import { PreferredVisualisationType } from '../../types/data'; +import { DataFrame, FieldType } from '../../types/dataFrame'; + +/** + * @alpha + */ +export interface PanelDataSummary { + hasData?: boolean; + rowCountTotal: number; + rowCountMax: number; + frameCount: number; + fieldCount: number; + fieldCountByType: (type: FieldType) => number; + hasFieldType: (type: FieldType) => boolean; + /** The first frame that set's this value */ + preferredVisualisationType?: PreferredVisualisationType; + + /* --- DEPRECATED FIELDS BELOW --- */ + /** @deprecated use PanelDataSummary.fieldCountByType(FieldType.number) */ + numberFieldCount: number; + /** @deprecated use PanelDataSummary.fieldCountByType(FieldType.time) */ + timeFieldCount: number; + /** @deprecated use PanelDataSummary.fieldCountByType(FieldType.string) */ + stringFieldCount: number; + /** @deprecated use PanelDataSummary.hasFieldType(FieldType.number) */ + hasNumberField?: boolean; + /** @deprecated use PanelDataSummary.hasFieldType(FieldType.time) */ + hasTimeField?: boolean; + /** @deprecated use PanelDataSummary.hasFieldType(FieldType.string) */ + hasStringField?: boolean; +} + +/** + * @alpha + * given a list of dataframes, summarize attributes of those frames for features like suggestions. + * @param frames - dataframes to summarize + * @returns summary of the dataframes + */ +export function getPanelDataSummary(frames: DataFrame[] = []): PanelDataSummary { + let rowCountTotal = 0; + let rowCountMax = 0; + let fieldCount = 0; + const countByType: Partial> = {}; + let preferredVisualisationType: PreferredVisualisationType | undefined; + + for (const frame of frames) { + rowCountTotal += frame.length; + + if (frame.meta?.preferredVisualisationType) { + preferredVisualisationType = frame.meta.preferredVisualisationType; + } + + for (const field of frame.fields) { + fieldCount++; + countByType[field.type] = (countByType[field.type] || 0) + 1; + } + + if (frame.length > rowCountMax) { + rowCountMax = frame.length; + } + } + + const fieldCountByType = (f: FieldType) => countByType[f] ?? 0; + + return { + rowCountTotal, + rowCountMax, + fieldCount, + preferredVisualisationType, + frameCount: frames.length, + hasData: rowCountTotal > 0, + hasFieldType: (f: FieldType) => fieldCountByType(f) > 0, + fieldCountByType, + // deprecated + numberFieldCount: fieldCountByType(FieldType.number), + timeFieldCount: fieldCountByType(FieldType.time), + stringFieldCount: fieldCountByType(FieldType.string), + hasTimeField: fieldCountByType(FieldType.time) > 0, + hasNumberField: fieldCountByType(FieldType.number) > 0, + hasStringField: fieldCountByType(FieldType.string) > 0, + }; +} diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 92991080976..376229c83f4 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -291,6 +291,7 @@ export interface GrafanaConfig { rudderstackIntegrationsUrl: string; applicationInsightsConnectionString: string; applicationInsightsEndpointUrl: string; + applicationInsightsAutoRouteTracking: boolean; analyticsConsoleReporting: boolean; rendererAvailable: boolean; rendererVersion: string; @@ -311,6 +312,7 @@ export interface GrafanaConfig { pluginCatalogHiddenPlugins: string[]; pluginCatalogManagedPlugins: string[]; pluginCatalogPreinstalledPlugins: PreinstalledPlugin[]; + pluginCatalogPreinstalledAutoUpdate?: boolean; pluginsCDNBaseURL: string; tokenExpirationDayLimit: number; listDashboardScopesEndpoint: string; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index eeddfb65a7b..2f8cc13e8df 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -50,11 +50,6 @@ export interface FeatureToggles { */ storage?: boolean; /** - * Correlations page - * @default true - */ - correlations?: boolean; - /** * Allow elements nesting */ canvasPanelNesting?: boolean; @@ -84,10 +79,6 @@ export interface FeatureToggles { */ alertingBacktesting?: boolean; /** - * Enables drag and drop for CSV and Excel files - */ - editPanelCSVDragAndDrop?: boolean; - /** * Allow datasource to provide custom UI for context view * @default true */ @@ -165,15 +156,6 @@ export interface FeatureToggles { */ extraThemes?: boolean; /** - * Enables the plugins frontend sandbox - */ - pluginsFrontendSandbox?: boolean; - /** - * Enables writing multiple items from a single query within Recorded Queries - * @default true - */ - recordedQueriesMulti?: boolean; - /** * A table visualisation for logs in Explore * @default true */ @@ -252,11 +234,6 @@ export interface FeatureToggles { */ externalServiceAccounts?: boolean; /** - * Enables panel monitoring through logs and measurements - * @default true - */ - panelMonitoring?: boolean; - /** * Enables native HTTP Histograms */ enableNativeHTTPHistogram?: boolean; @@ -377,10 +354,6 @@ export interface FeatureToggles { */ annotationPermissionUpdate?: boolean; /** - * Make sure extracted field names are unique in the dataframe - */ - extractFieldsNameDeduplication?: boolean; - /** * Enables dashboard rendering using Scenes for viewer roles * @default true */ @@ -523,11 +496,6 @@ export interface FeatureToggles { */ newPDFRendering?: boolean; /** - * Use TLS-enabled memcached in the enterprise caching feature - * @default true - */ - tlsMemcached?: boolean; - /** * Enable grafana's embedded kube-aggregator */ kubernetesAggregator?: boolean; @@ -569,10 +537,14 @@ export interface FeatureToggles { */ queryLibrary?: boolean; /** - * Enable suggested dashboards when creating new dashboards + * Enable dashboard library experiments that are production ready */ dashboardLibrary?: boolean; /** + * Enable suggested dashboards when creating new dashboards + */ + suggestedDashboards?: boolean; + /** * Sets the logs table as default visualisation in logs explore */ logsExploreTableDefaultVisualization?: boolean; @@ -729,16 +701,15 @@ export interface FeatureToggles { */ timeRangeProvider?: boolean; /** + * Enables time range panning functionality + */ + timeRangePan?: boolean; + /** * Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. * @default false */ azureMonitorDisableLogLimit?: boolean; /** - * Enables automatic updates for pre-installed plugins - * @default true - */ - preinstallAutoUpdate?: boolean; - /** * Enables experimental reconciler for playlists */ playlistsReconciler?: boolean; @@ -863,10 +834,6 @@ export interface FeatureToggles { */ teamHttpHeadersTempo?: boolean; /** - * Use new **Combobox** component for template variables - */ - templateVariablesUsesCombobox?: boolean; - /** * Enables Advisor app */ grafanaAdvisor?: boolean; @@ -1125,11 +1092,6 @@ export interface FeatureToggles { */ unifiedStorageSearchDualReaderEnabled?: boolean; /** - * Enables adhoc filtering support for the dashboard datasource - * @default true - */ - dashboardDsAdHocFiltering?: boolean; - /** * Supports __from and __to macros that always use the dashboard level time range */ dashboardLevelTimeMacros?: boolean; @@ -1143,11 +1105,6 @@ export interface FeatureToggles { */ restrictedPluginApis?: boolean; /** - * Enable adhoc filter buttons in visualization tooltips - * @default true - */ - adhocFiltersInTooltips?: boolean; - /** * Enable favorite datasources */ favoriteDatasources?: boolean; @@ -1220,6 +1177,11 @@ export interface FeatureToggles { */ newGauge?: boolean; /** + * Enable new visualization suggestions + * @default false + */ + newVizSuggestions?: boolean; + /** * Restrict PanelChrome contents with overflow: hidden; * @default true */ @@ -1234,6 +1196,11 @@ export interface FeatureToggles { */ pluginStoreServiceLoading?: boolean; /** + * Increases panel padding globally + * @default false + */ + newPanelPadding?: boolean; + /** * When storing dashboard and folder resource permissions, only store action sets and not the full list of underlying permission * @default true */ @@ -1246,4 +1213,18 @@ export interface FeatureToggles { * Enable template dashboards */ dashboardTemplates?: boolean; + /** + * Enables Advisor app installer + */ + grafanaAdvisorAppInstaller?: boolean; + /** + * Enables app platform API for annotations + * @default false + */ + kubernetesAnnotations?: boolean; + /** + * Enables http proxy settings for aws datasources + * @default false + */ + awsDatasourcesHttpProxy?: boolean; } diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 6a6f91be45c..b49be0d5363 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -162,6 +162,7 @@ export const availableIconsIndex = { globe: true, grafana: true, 'graph-bar': true, + 'hand-pointer': true, heart: true, 'heart-rate': true, 'heart-break': true, diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 91083493c83..2664d0bec72 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -1,5 +1,3 @@ -import { defaultsDeep } from 'lodash'; - import { EventBus } from '../events/types'; import { StandardEditorProps } from '../field/standardFieldConfigEditorRegistry'; import { Registry } from '../utils/Registry'; @@ -8,15 +6,14 @@ import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; import { ScopedVars } from './ScopedVars'; import { AlertStateInfo } from './alerts'; import { PanelModel } from './dashboard'; -import { LoadingState, PreferredVisualisationType } from './data'; -import { DataFrame, FieldType } from './dataFrame'; +import { LoadingState } from './data'; +import { DataFrame } from './dataFrame'; import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource'; import { FieldConfigSource } from './fieldOverrides'; import { IconName } from './icon'; import { OptionEditorConfig } from './options'; import { PluginMeta } from './plugin'; import { AbsoluteTimeRange, TimeRange, TimeZone } from './time'; -import { DataTransformerConfig } from './transformations'; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; @@ -218,169 +215,3 @@ export interface PanelPluginDataSupport { annotations: boolean; alertStates: boolean; } - -/** - * @alpha - */ -export interface VisualizationSuggestion { - /** Name of suggestion */ - name: string; - /** Description */ - description?: string; - /** Panel plugin id */ - pluginId: string; - /** Panel plugin options */ - options?: Partial; - /** Panel plugin field options */ - fieldConfig?: FieldConfigSource>; - /** Data transformations */ - transformations?: DataTransformerConfig[]; - /** Options for how to render suggestion card */ - cardOptions?: { - /** Tweak for small preview */ - previewModifier?: (suggestion: VisualizationSuggestion) => void; - icon?: string; - imgSrc?: string; - }; - /** A value between 0-100 how suitable suggestion is */ - score?: VisualizationSuggestionScore; -} - -/** - * @alpha - */ -export enum VisualizationSuggestionScore { - /** We are pretty sure this is the best possible option */ - Best = 100, - /** Should be a really good option */ - Good = 70, - /** Can be visualized but there are likely better options. If no score is set this score is assumed */ - OK = 50, -} - -/** - * @alpha - */ -export interface PanelDataSummary { - hasData?: boolean; - rowCountTotal: number; - rowCountMax: number; - frameCount: number; - fieldCount: number; - numberFieldCount: number; - timeFieldCount: number; - stringFieldCount: number; - hasNumberField?: boolean; - hasTimeField?: boolean; - hasStringField?: boolean; - /** The first frame that set's this value */ - preferredVisualisationType?: PreferredVisualisationType; -} - -/** - * @alpha - */ -export class VisualizationSuggestionsBuilder { - /** Current data */ - data?: PanelData; - /** Current panel & options */ - panel?: PanelModel; - /** Summary stats for current data */ - dataSummary: PanelDataSummary; - - private list: VisualizationSuggestion[] = []; - - constructor(data?: PanelData, panel?: PanelModel) { - this.data = data; - this.panel = panel; - this.dataSummary = this.computeDataSummary(); - } - - getListAppender(defaults: VisualizationSuggestion) { - return new VisualizationSuggestionsListAppender(this.list, defaults); - } - - private computeDataSummary() { - const frames = this.data?.series || []; - - let numberFieldCount = 0; - let timeFieldCount = 0; - let stringFieldCount = 0; - let rowCountTotal = 0; - let rowCountMax = 0; - let fieldCount = 0; - let preferredVisualisationType: PreferredVisualisationType | undefined; - - for (const frame of frames) { - rowCountTotal += frame.length; - - if (frame.meta?.preferredVisualisationType) { - preferredVisualisationType = frame.meta.preferredVisualisationType; - } - - for (const field of frame.fields) { - fieldCount++; - - switch (field.type) { - case FieldType.number: - numberFieldCount += 1; - break; - case FieldType.time: - timeFieldCount += 1; - break; - case FieldType.string: - stringFieldCount += 1; - break; - } - } - - if (frame.length > rowCountMax) { - rowCountMax = frame.length; - } - } - - return { - numberFieldCount, - timeFieldCount, - stringFieldCount, - rowCountTotal, - rowCountMax, - fieldCount, - preferredVisualisationType, - frameCount: frames.length, - hasData: rowCountTotal > 0, - hasTimeField: timeFieldCount > 0, - hasNumberField: numberFieldCount > 0, - hasStringField: stringFieldCount > 0, - }; - } - - getList() { - return this.list; - } -} - -/** - * @alpha - */ -export type VisualizationSuggestionsSupplier = { - /** - * Adds good suitable suggestions for the current data - */ - getSuggestionsForData: (builder: VisualizationSuggestionsBuilder) => void; -}; - -/** - * Helps with typings and defaults - * @alpha - */ -export class VisualizationSuggestionsListAppender { - constructor( - private list: VisualizationSuggestion[], - private defaults: VisualizationSuggestion - ) {} - - append(overrides: Partial>) { - this.list.push(defaultsDeep(overrides, this.defaults)); - } -} diff --git a/packages/grafana-data/src/types/suggestions.ts b/packages/grafana-data/src/types/suggestions.ts new file mode 100644 index 00000000000..a7b2611a990 --- /dev/null +++ b/packages/grafana-data/src/types/suggestions.ts @@ -0,0 +1,100 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { defaultsDeep } from 'lodash'; + +import { DataTransformerConfig } from '@grafana/schema'; + +import { PanelDataSummary, getPanelDataSummary } from '../panel/suggestions/getPanelDataSummary'; + +import { PanelModel } from './dashboard'; +import { FieldConfigSource } from './fieldOverrides'; +import { PanelData } from './panel'; + +/** + * @alpha + */ +export interface VisualizationSuggestion { + /** Name of suggestion */ + name: string; + /** Description */ + description?: string; + /** Panel plugin id */ + pluginId: string; + /** Panel plugin options */ + options?: Partial; + /** Panel plugin field options */ + fieldConfig?: FieldConfigSource>; + /** Data transformations */ + transformations?: DataTransformerConfig[]; + /** Options for how to render suggestion card */ + cardOptions?: { + /** Tweak for small preview */ + previewModifier?: (suggestion: VisualizationSuggestion) => void; + icon?: string; + imgSrc?: string; + }; + /** A value between 0-100 how suitable suggestion is */ + score?: VisualizationSuggestionScore; +} + +/** + * @alpha + */ +export enum VisualizationSuggestionScore { + /** We are pretty sure this is the best possible option */ + Best = 100, + /** Should be a really good option */ + Good = 70, + /** Can be visualized but there are likely better options. If no score is set this score is assumed */ + OK = 50, +} + +/** + * @alpha + */ +export class VisualizationSuggestionsBuilder { + /** Summary stats for current data */ + dataSummary: PanelDataSummary; + private list: VisualizationSuggestion[] = []; + + constructor( + /** Current data */ + public data?: PanelData, + /** Current panel & options */ + public panel?: PanelModel + ) { + this.dataSummary = getPanelDataSummary(data?.series); + } + + getListAppender(defaults: VisualizationSuggestion) { + return new VisualizationSuggestionsListAppender(this.list, defaults); + } + + getList() { + return this.list; + } +} + +/** + * @alpha + */ +export type VisualizationSuggestionsSupplier = { + /** + * Adds good suitable suggestions for the current data + */ + getSuggestionsForData: (builder: VisualizationSuggestionsBuilder) => void; +}; + +/** + * Helps with typings and defaults + * @alpha + */ +export class VisualizationSuggestionsListAppender { + constructor( + private list: VisualizationSuggestion[], + private defaults: VisualizationSuggestion + ) {} + + append(overrides: Partial>) { + this.list.push(defaultsDeep(overrides, this.defaults)); + } +} diff --git a/packages/grafana-data/src/utils/csv.ts b/packages/grafana-data/src/utils/csv.ts index 7c225e47ac0..9823aa4c550 100644 --- a/packages/grafana-data/src/utils/csv.ts +++ b/packages/grafana-data/src/utils/csv.ts @@ -115,7 +115,7 @@ export class CSVReader { if (!fields[j].config) { fields[j].config = {}; } - const disp = fields[j].config as any; // any lets name lookup + const disp: any = fields[j].config; // any lets name lookup disp[k] = j === 0 ? v : line[j]; } } diff --git a/packages/grafana-data/src/utils/dataLinks.test.ts b/packages/grafana-data/src/utils/dataLinks.test.ts index 86e01f4139c..379e678fd90 100644 --- a/packages/grafana-data/src/utils/dataLinks.test.ts +++ b/packages/grafana-data/src/utils/dataLinks.test.ts @@ -46,7 +46,7 @@ describe('mapInternalLinkToExplore', () => { expect(link).toEqual( expect.objectContaining({ title: 'dsName', - href: `/explore?left=${encodeURIComponent('{"datasource":"uid","queries":[{"query":"12344"}]}')}`, + href: `/explore?left=${encodeURIComponent('{"datasource":"uid","queries":[{"query":"12344","datasource":{"uid":"uid"}}]}')}`, onClick: undefined, interpolatedParams: { query: { @@ -95,7 +95,7 @@ describe('mapInternalLinkToExplore', () => { expect.objectContaining({ title: 'dsName', href: `/explore?left=${encodeURIComponent( - '{"datasource":"uid","queries":[{"query":"12344"}],"panelsState":{"trace":{"spanId":"abcdef"}}}' + '{"datasource":"uid","queries":[{"query":"12344","datasource":{"uid":"uid"}}],"panelsState":{"trace":{"spanId":"abcdef"}}}' )}`, onClick: undefined, }) @@ -144,6 +144,7 @@ describe('mapInternalLinkToExplore', () => { nested: { something: 'val1' }, num: 1, arr: ['val1', 'non var'], + datasource: { uid: 'uid' }, }; expect(decodeURIComponent(link.href)).toEqual( @@ -158,9 +159,6 @@ describe('mapInternalLinkToExplore', () => { ); expect(link.interpolatedParams?.query).toEqual({ - datasource: { - uid: 'uid', - }, ...query, }); diff --git a/packages/grafana-data/src/utils/dataLinks.ts b/packages/grafana-data/src/utils/dataLinks.ts index ece36cf525a..a9b38e1df15 100644 --- a/packages/grafana-data/src/utils/dataLinks.ts +++ b/packages/grafana-data/src/utils/dataLinks.ts @@ -42,6 +42,15 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod typeof link.internal?.query === 'function' ? link.internal.query({ replaceVariables, scopedVars }) : internalLink.query; + + // datasource ref is optional in a query object, but Explore relies on it being defined for some + // functionalities, e.g., changing query filters directly from visualizations, so we need to put + // it here if it's missing. See also #112945 + if (query && typeof query === 'object' && !query.datasource?.uid && internalLink.datasourceUid) { + query.datasource = query.datasource || {}; + query.datasource.uid = internalLink.datasourceUid; + } + const interpolatedQuery = interpolateObject(query, scopedVars, replaceVariables); const interpolatedPanelsState = interpolateObject(link.internal?.panelsState, scopedVars, replaceVariables); const interpolatedCorrelationData = interpolateObject(link.meta?.correlationData, scopedVars, replaceVariables); @@ -49,14 +58,7 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod const interpolatedParams = interpolatedQuery ? { - query: { - ...interpolatedQuery, - // data source is defined in a separate property in DataLink, we ensure it's put back together after interpolation - datasource: { - ...interpolatedQuery.datasource, - uid: internalLink.datasourceUid, - }, - }, + query: interpolatedQuery, ...(range && { timeRange: range }), } : undefined; diff --git a/packages/grafana-data/src/utils/throwIfAngular.test.ts b/packages/grafana-data/src/utils/throwIfAngular.test.ts index 8cdbae71cd9..8b17e2f10e6 100644 --- a/packages/grafana-data/src/utils/throwIfAngular.test.ts +++ b/packages/grafana-data/src/utils/throwIfAngular.test.ts @@ -33,6 +33,7 @@ describe('throwIfAngular', () => { it('should throw if angular panel', () => { const underTest = new PanelPlugin(null); + // @ts-expect-error underTest.angularPanelCtrl = {}; expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported'); }); diff --git a/packages/grafana-data/src/vector/CircularVector.ts b/packages/grafana-data/src/vector/CircularVector.ts index 221f7c488e5..70b570630bc 100644 --- a/packages/grafana-data/src/vector/CircularVector.ts +++ b/packages/grafana-data/src/vector/CircularVector.ts @@ -16,7 +16,7 @@ interface CircularOptions { * @public * @deprecated use a simple Arrays */ -export class CircularVector extends FunctionalVector { +export class CircularVector extends FunctionalVector { private buffer: T[]; private index: number; private capacity: number; diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 9a50bc2ee5c..7a8109b8a57 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-eslint-rules/README.md b/packages/grafana-eslint-rules/README.md index be49122b758..aa6d4e880ff 100644 --- a/packages/grafana-eslint-rules/README.md +++ b/packages/grafana-eslint-rules/README.md @@ -140,3 +140,44 @@ export default storyConfig; const storyConfig = { title: 'Components/Forms/Button' }; export default storyConfig; ``` + +### `no-plugin-external-import-paths` + +Prevent plugins from importing anything outside their own directory. + +This rule enforces strict plugin isolation by preventing plugins from importing anything that reaches outside their own plugin directory. This helps maintain clean plugin boundaries and prevents tight coupling between plugins and other parts of the codebase. + +The rule automatically detects the current plugin directory from the file path and blocks any relative imports that would reach outside that directory. + +The rule is applied to specific plugins by configuring the `files` pattern in the ESLint configuration, similar to `grafana/decoupled-plugins-overrides`. + +#### Examples + +```tsx +// Bad ❌ - Importing from sibling plugin +import { getDataLinks } from '../status-history/utils'; +import { isTooltipScrollable } from '../timeseries/utils'; + +// Bad ❌ - Importing from Grafana core +import { something } from '../../../features/dashboard/state'; + +// Bad ❌ - Importing from outside plugin directory +import { other } from '../some-other-folder/utils'; + +// Good ✅ - Importing from same plugin +import { someUtil } from './utils'; +import { Component } from './Component'; +import { helper } from './subfolder/helper'; + +// Good ✅ - Importing from external packages +import React from 'react'; +import { Button } from '@grafana/ui'; +``` + +#### Error Message + +When a violation is detected, the rule reports: + +``` +Import '../status-history/utils' reaches outside the 'histogram' plugin directory. Plugins should only import from external dependencies or relative paths within their own directory. +``` diff --git a/packages/grafana-eslint-rules/index.cjs b/packages/grafana-eslint-rules/index.cjs index 438d88332bc..89a76ee60a1 100644 --- a/packages/grafana-eslint-rules/index.cjs +++ b/packages/grafana-eslint-rules/index.cjs @@ -4,6 +4,7 @@ const noUnreducedMotion = require('./rules/no-unreduced-motion.cjs'); const themeTokenUsage = require('./rules/theme-token-usage.cjs'); const noRestrictedImgSrcs = require('./rules/no-restricted-img-srcs.cjs'); const consistentStoryTitles = require('./rules/consistent-story-titles.cjs'); +const noPluginExternalImportPaths = require('./rules/no-plugin-external-import-paths.cjs'); module.exports = { rules: { @@ -13,5 +14,6 @@ module.exports = { 'theme-token-usage': themeTokenUsage, 'no-restricted-img-srcs': noRestrictedImgSrcs, 'consistent-story-titles': consistentStoryTitles, + 'no-plugin-external-import-paths': noPluginExternalImportPaths, }, }; diff --git a/packages/grafana-eslint-rules/jest.config.js b/packages/grafana-eslint-rules/jest.config.js new file mode 100644 index 00000000000..757e62524d6 --- /dev/null +++ b/packages/grafana-eslint-rules/jest.config.js @@ -0,0 +1,4 @@ +export default { + testEnvironment: 'node', + testMatch: ['/**/*.test.js'], +}; diff --git a/packages/grafana-eslint-rules/package.json b/packages/grafana-eslint-rules/package.json index f182cd0281b..0fe0ca61193 100644 --- a/packages/grafana-eslint-rules/package.json +++ b/packages/grafana-eslint-rules/package.json @@ -1,12 +1,14 @@ { "name": "@grafana/eslint-plugin", "description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.", - "version": "12.3.0-pre", + "version": "12.4.0-pre", + "type": "module", "main": "./index.cjs", "author": "Grafana Labs", "license": "Apache-2.0", "scripts": { - "typecheck": "tsc --emitDeclarationOnly false --noEmit" + "typecheck": "tsc --emitDeclarationOnly false --noEmit", + "test": "NODE_OPTIONS='--experimental-vm-modules' jest" }, "repository": { "type": "git", @@ -19,6 +21,7 @@ "devDependencies": { "@typescript-eslint/types": "^8.9.0", "eslint": "9.32.0", + "jest": "29.7.0", "tslib": "2.8.1" }, "private": true diff --git a/packages/grafana-eslint-rules/rules/no-plugin-external-import-paths.cjs b/packages/grafana-eslint-rules/rules/no-plugin-external-import-paths.cjs new file mode 100644 index 00000000000..63f44fe3077 --- /dev/null +++ b/packages/grafana-eslint-rules/rules/no-plugin-external-import-paths.cjs @@ -0,0 +1,98 @@ +// @ts-check +/** @typedef {import('@typescript-eslint/utils').TSESTree.ImportDeclaration} ImportDeclaration */ +const { ESLintUtils } = require('@typescript-eslint/utils'); +const path = require('path'); + +const createRule = ESLintUtils.RuleCreator( + (name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}` +); + +/** + * Extract the plugin root directory from the file path + * @param {string} filePath - The file path being linted + * @returns {string|null} - The plugin root directory or null if not in a plugin directory + */ +function getPluginRootDirectory(filePath) { + const pluginMatch = filePath.match(/\/plugins\/(?:panel|datasource)\/([^/]+)\//); + if (pluginMatch) { + const pluginName = pluginMatch[1]; + const pluginType = pluginMatch[0].includes('/panel/') ? 'panel' : 'datasource'; + + const pluginDirPath = `/plugins/${pluginType}/${pluginName}`; + const pluginDirStart = filePath.indexOf(pluginDirPath); + if (pluginDirStart !== -1) { + const pluginRoot = filePath.substring(0, pluginDirStart + pluginDirPath.length); + return path.isAbsolute(pluginRoot) ? pluginRoot : path.resolve(pluginRoot); + } + } + return null; +} + +/** + * Check if an import path reaches outside the plugin's root directory boundaries + * @param {string} importPath - The import path to check + * @param {string} currentFilePath - The current file path being linted + * @param {string} pluginRoot - The plugin root directory + * @returns {boolean} - True if the import goes outside plugin boundaries + */ +function isImportOutsidePluginBoundaries(importPath, currentFilePath, pluginRoot) { + const isRelativeImport = importPath.startsWith('./') || importPath.startsWith('../'); + if (!isRelativeImport) { + return false; + } + + const currentDir = path.dirname(currentFilePath); + const resolvedPath = path.resolve(currentDir, importPath); + + const normalizedResolvedPath = path.normalize(resolvedPath); + const normalizedPluginRoot = path.normalize(pluginRoot); + + return !normalizedResolvedPath.startsWith(normalizedPluginRoot); +} + +const noRestrictedPeerPluginPathsRule = createRule({ + create(context) { + const currentFilePath = context.getFilename(); + const pluginRoot = getPluginRootDirectory(currentFilePath); + + if (!pluginRoot) { + return {}; + } + + return { + /** @param {ImportDeclaration} node */ + ImportDeclaration(node) { + const importPath = node.source.value; + + if ( + typeof importPath === 'string' && + isImportOutsidePluginBoundaries(importPath, currentFilePath, pluginRoot) + ) { + return context.report({ + node: node.source, + messageId: 'importOutsidePluginBoundaries', + data: { + importPath, + pluginRoot: path.basename(pluginRoot), + }, + }); + } + }, + }; + }, + name: 'no-plugin-external-import-paths', + meta: { + type: 'problem', + docs: { + description: 'Disallow imports that reach outside plugin root directory boundaries', + }, + messages: { + importOutsidePluginBoundaries: + "Import '{{importPath}}' reaches outside the '{{pluginRoot}}' plugin directory. Plugins should only import from external dependencies or relative paths within their own directory.", + }, + schema: [], + }, + defaultOptions: [], +}); + +module.exports = noRestrictedPeerPluginPathsRule; diff --git a/packages/grafana-eslint-rules/tests/no-plugin-external-import-paths.test.js b/packages/grafana-eslint-rules/tests/no-plugin-external-import-paths.test.js new file mode 100644 index 00000000000..3c25742d3eb --- /dev/null +++ b/packages/grafana-eslint-rules/tests/no-plugin-external-import-paths.test.js @@ -0,0 +1,81 @@ +import { RuleTester } from 'eslint'; + +import rule from '../rules/no-plugin-external-import-paths.cjs'; + +RuleTester.setDefaultConfig({ + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, +}); + +const ruleTester = new RuleTester(); + +ruleTester.run('eslint no-plugin-external-import-paths', rule, { + valid: [ + { + name: 'external npm package import', + filename: 'public/app/plugins/panel/histogram/HistogramTooltip.tsx', + code: "import React from 'react';", + }, + { + name: 'grafana package import', + filename: 'public/app/plugins/panel/histogram/HistogramTooltip.tsx', + code: "import { Button } from '@grafana/ui';", + }, + { + name: 'same plugin file import', + filename: 'public/app/plugins/panel/histogram/HistogramTooltip.tsx', + code: "import { someUtil } from './utils';", + }, + { + name: 'same plugin subdirectory import', + filename: 'public/app/plugins/panel/histogram/components/HistogramTooltip.tsx', + code: "import { Component } from '../Component';", + }, + ], + invalid: [ + { + name: 'sibling plugin import', + filename: 'public/app/plugins/panel/histogram/HistogramTooltip.tsx', + code: "import { getDataLinks } from '../status-history/utils';", + errors: [ + { + messageId: 'importOutsidePluginBoundaries', + data: { + importPath: '../status-history/utils', + pluginRoot: 'histogram', + }, + }, + ], + }, + { + name: 'grafana core import', + filename: 'public/app/plugins/panel/histogram/HistogramTooltip.tsx', + code: "import { something } from '../../../features/dashboard/state';", + errors: [ + { + messageId: 'importOutsidePluginBoundaries', + data: { + importPath: '../../../features/dashboard/state', + pluginRoot: 'histogram', + }, + }, + ], + }, + { + name: 'datasource plugin sibling import', + filename: 'public/app/plugins/datasource/loki/datasource.ts', + code: "import { something } from '../prometheus/utils';", + errors: [ + { + messageId: 'importOutsidePluginBoundaries', + data: { + importPath: '../prometheus/utils', + pluginRoot: 'loki', + }, + }, + ], + }, + ], +}); diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 98cb22e9fb2..d450f911e70 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/flamegraph", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana flamegraph visualization component", "keywords": [ "grafana", @@ -44,8 +44,8 @@ ], "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.3.0-pre", - "@grafana/ui": "12.3.0-pre", + "@grafana/data": "12.4.0-pre", + "@grafana/ui": "12.4.0-pre", "@leeoniya/ufuzzy": "1.0.19", "d3": "^7.8.5", "lodash": "4.17.21", @@ -83,7 +83,7 @@ "typescript": "5.9.2" }, "peerDependencies": { - "@grafana/assistant": "^0.1.0", + "@grafana/assistant": "^0.1.1", "react": "^18.0.0", "react-dom": "^18.0.0" } diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index ba8d2311acf..824f594f8e6 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/i18n", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Internationalization Library", "keywords": [ "grafana", diff --git a/packages/grafana-i18n/src/types/dates.d.ts b/packages/grafana-i18n/src/types/dates.d.ts index 7a4ac64930c..3d728f70d85 100644 --- a/packages/grafana-i18n/src/types/dates.d.ts +++ b/packages/grafana-i18n/src/types/dates.d.ts @@ -1,4 +1,4 @@ -import { +import type { DurationFormatConstructor, DurationFormatOptions as _DurationFormatOptions, DurationInput as _DurationInput, diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index b79a584715f..569b7e60c88 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "name": "@grafana/o11y-ds-frontend", "private": true, - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Library to manage traces in Grafana.", "sideEffects": false, "repository": { @@ -18,12 +18,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.3.0-pre", - "@grafana/e2e-selectors": "12.3.0-pre", + "@grafana/data": "12.4.0-pre", + "@grafana/e2e-selectors": "12.4.0-pre", "@grafana/plugin-ui": "^0.10.10", - "@grafana/runtime": "12.3.0-pre", - "@grafana/schema": "12.3.0-pre", - "@grafana/ui": "12.3.0-pre", + "@grafana/runtime": "12.4.0-pre", + "@grafana/schema": "12.4.0-pre", + "@grafana/ui": "12.4.0-pre", "react-select": "5.10.2", "react-use": "17.6.0", "rxjs": "7.8.2", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index 84a74940dbf..68400c19b92 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -2,7 +2,7 @@ "name": "@grafana/plugin-configs", "description": "Shared dependencies and files for core plugins", "private": true, - "version": "12.3.0-pre", + "version": "12.4.0-pre", "dependencies": { "react": "18.3.1", "terser-webpack-plugin": "5.3.14", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 4c8f16ac688..a0dba4898e9 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "AGPL-3.0-only", "name": "@grafana/prometheus", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Prometheus Library", "keywords": [ "typescript", @@ -41,18 +41,18 @@ "dependencies": { "@emotion/css": "11.13.5", "@floating-ui/react": "0.27.16", - "@grafana/data": "12.3.0-pre", - "@grafana/e2e-selectors": "12.3.0-pre", - "@grafana/i18n": "12.3.0-pre", + "@grafana/data": "12.4.0-pre", + "@grafana/e2e-selectors": "12.4.0-pre", + "@grafana/i18n": "12.4.0-pre", "@grafana/plugin-ui": "^0.10.10", - "@grafana/runtime": "12.3.0-pre", - "@grafana/schema": "12.3.0-pre", - "@grafana/ui": "12.3.0-pre", + "@grafana/runtime": "12.4.0-pre", + "@grafana/schema": "12.4.0-pre", + "@grafana/ui": "12.4.0-pre", "@hello-pangea/dnd": "18.0.1", "@leeoniya/ufuzzy": "1.0.19", "@lezer/common": "1.2.3", "@lezer/highlight": "1.2.1", - "@lezer/lr": "1.4.2", + "@lezer/lr": "1.4.3", "@prometheus-io/lezer-promql": "0.305.0", "@reduxjs/toolkit": "2.9.0", "@types/debounce-promise": "3.1.9", diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx index 17347c7a20b..3468be71316 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.tsx @@ -6,8 +6,8 @@ import * as React from 'react'; import { CoreApp, SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { EditorField, EditorRow, EditorSwitch } from '@grafana/plugin-ui'; -import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui'; +import { EditorField, EditorSwitch } from '@grafana/plugin-ui'; +import { AutoSizeInput, Box, RadioButtonGroup, Select } from '@grafana/ui'; import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField'; import { PromQueryFormat } from '../../dataquery'; @@ -80,7 +80,7 @@ export const PromQueryBuilderOptions = React.memo( const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryTypeValue)!.label; return ( - +
( )}
-
+ ); } ); diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index f9ba7ce203d..a46d08d4623 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -53,11 +53,11 @@ "postpack": "mv package.json.bak package.json && rimraf ./unstable" }, "dependencies": { - "@grafana/data": "12.3.0-pre", - "@grafana/e2e-selectors": "12.3.0-pre", + "@grafana/data": "12.4.0-pre", + "@grafana/e2e-selectors": "12.4.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "12.3.0-pre", - "@grafana/ui": "12.3.0-pre", + "@grafana/schema": "12.4.0-pre", + "@grafana/ui": "12.4.0-pre", "@openfeature/core": "^1.9.0", "@openfeature/ofrep-web-provider": "^0.3.3", "@openfeature/web-sdk": "^1.6.1", diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index bf36333e107..4c7a2714cd6 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -169,6 +169,7 @@ export class GrafanaBootConfig { pluginCatalogHiddenPlugins: string[] = []; pluginCatalogManagedPlugins: string[] = []; pluginCatalogPreinstalledPlugins: PreinstalledPluginGrafanaData[] = []; + pluginCatalogPreinstalledAutoUpdate?: boolean; pluginsCDNBaseURL = ''; expressionsEnabled = false; awsAllowedAuthProviders: string[] = []; @@ -182,6 +183,7 @@ export class GrafanaBootConfig { }; caching = { enabled: false, + cleanCacheEnabled: true, }; geomapDefaultBaseLayerConfig?: MapLayerOptions; geomapDisableCustomBaseLayer?: boolean; @@ -203,6 +205,7 @@ export class GrafanaBootConfig { }; applicationInsightsConnectionString?: string; applicationInsightsEndpointUrl?: string; + applicationInsightsAutoRouteTracking?: boolean; recordedQueries = { enabled: true, }; @@ -279,6 +282,8 @@ export class GrafanaBootConfig { overrideFeatureTogglesFromUrl(this); overrideFeatureTogglesFromLocalStorage(this); + this.bootData.settings.featureToggles = this.featureToggles; + // Creating theme after applying feature toggle overrides in case we need to toggle anything this.theme2 = getThemeById(this.bootData.user.theme); this.bootData.user.lightTheme = this.theme2.isLight; diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index feff0eb8ba3..067475cd209 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index 90434500881..8188c467ed8 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -502,6 +502,20 @@ export enum VizOrientation { Vertical = 'vertical', } +/** + * Breaks out each annotation frame into multiple lanes on the x-axis + */ +export interface VizAnnotations { + multiLane?: boolean; +} + +/** + * TODO docs + */ +export interface OptionsWithAnnotations { + annotations?: VizAnnotations; +} + /** * TODO docs */ diff --git a/packages/grafana-schema/src/common/mudball.cue b/packages/grafana-schema/src/common/mudball.cue index 24c371d8715..6aeca5ea925 100644 --- a/packages/grafana-schema/src/common/mudball.cue +++ b/packages/grafana-schema/src/common/mudball.cue @@ -158,6 +158,16 @@ ReduceDataOptions: { // TODO docs VizOrientation: "auto" | "vertical" | "horizontal" @cuetsy(kind="enum") +// Breaks out each annotation frame into multiple lanes on the x-axis +VizAnnotations: { + multiLane?: bool +} @cuetsy(kind="interface") + +// TODO docs +OptionsWithAnnotations: { + annotations?: VizAnnotations +} @cuetsy(kind="interface") + // TODO docs OptionsWithTooltip: { tooltip: VizTooltipOptions diff --git a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts index c9d2e73432e..715397acd61 100644 --- a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { limit: number; diff --git a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts index 275d5d7c921..e01120da861 100644 --- a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting { /** diff --git a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts index dcf792aadbd..542944b0e3a 100644 --- a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions { displayMode: common.BarGaugeDisplayMode; diff --git a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts index ab38df01ee6..18b9cb91f71 100644 --- a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export enum VizDisplayMode { Candles = 'candles', @@ -63,7 +63,7 @@ export const defaultCandlestickColors: Partial = { up: 'green', }; -export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { +export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithAnnotations { /** * Sets the style of the candlesticks */ diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 13939c556d6..fea3266ca6d 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export enum HorizontalConstraint { Center = 'center', diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index 61cb1e34254..eb0dd667076 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface MetricStat { /** @@ -247,7 +247,7 @@ export interface CloudWatchLogsQuery extends common.DataQuery { */ logGroups?: Array; /** - * Whether a query is a Logs Insights or Logs Anomalies query + * Whether a query is a Logs Insights or Log Anomalies query */ logsMode?: LogsMode; /** @@ -275,7 +275,7 @@ export const defaultCloudWatchLogsQuery: Partial = { }; /** - * Shape of a Cloudwatch Logs Anomalies query + * Shape of a Cloudwatch Log Anomalies query */ export interface CloudWatchLogsAnomaliesQuery extends common.DataQuery { /** @@ -284,7 +284,7 @@ export interface CloudWatchLogsAnomaliesQuery extends common.DataQuery { anomalyDetectionARN?: string; id: string; /** - * Whether a query is a Logs Insights or Logs Anomalies query + * Whether a query is a Logs Insights or Log Anomalies query */ logsMode?: LogsMode; /** diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index a1ec65bf148..5b65d042e72 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts index 65ef7e9883d..cfc4efd5f22 100644 --- a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { selectedSeries: number; diff --git a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts index 443add70b78..d71d823d38f 100644 --- a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export type UpdateConfig = { render: boolean, diff --git a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts index d63d368d534..ad46e229611 100644 --- a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested); diff --git a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts index ac1fd6808c2..f68950c8cb3 100644 --- a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options extends common.SingleStatBaseOptions { minVizHeight: number; diff --git a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts index 4819a4cfc1b..74bdafa169d 100644 --- a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { basemap: ui.MapLayerOptions; diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index d905b1bd2ea..1ff4370c9a2 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; /** * Controls the color mode of the heatmap @@ -188,6 +188,7 @@ export interface RowsHeatmapOptions { } export interface Options { + annotations?: ui.VizAnnotations; /** * Controls if the heatmap should be calculated from data */ diff --git a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts index 2d5705e31e5..aa104192e1a 100644 --- a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { /** diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index 9ea5e96bad3..f8e672a8979 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { controlsStorageKey?: string; diff --git a/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts index 045b217482f..5f604cebfae 100644 --- a/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface GaugePanelEffects { barGlow?: boolean; diff --git a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts index 5f46545f820..e26eabbdee2 100644 --- a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts index 17fe57fcc44..0c980fc8495 100644 --- a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface ArcOption { /** diff --git a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts index 067c6d7d146..8a3f0bbe55c 100644 --- a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; /** * Select the pie chart display style. diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index 0fb48020d23..f12b46fd8f0 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; diff --git a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts index b8220d24d3a..1c327e1b8c0 100644 --- a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts @@ -10,9 +10,9 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; -export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { +export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones, ui.OptionsWithAnnotations { /** * Controls value alignment on the timelines */ diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index 030a310ff8b..48825cb2b58 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -10,9 +10,9 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; -export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { +export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones, ui.OptionsWithAnnotations { /** * Controls the column width */ diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index 3964c70f630..c45151fed2b 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts index 39053ebb9af..c4695995725 100644 --- a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export enum TextMode { Code = 'code', diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index 538860716da..d0213051004 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -10,9 +10,9 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; -export interface Options extends common.OptionsWithTimezones { +export interface Options extends common.OptionsWithTimezones, common.OptionsWithAnnotations { legend: common.VizLegendOptions; orientation?: common.VizOrientation; timeCompare?: common.TimeCompareOptions; diff --git a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts index 7ec6ddd5686..fc0bcd92a4d 100644 --- a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; /** * Identical to timeseries... except it does not have timezone settings diff --git a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts index 450721304fe..5e851caa415 100644 --- a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.3.0-pre"; +export const pluginVersion = "12.4.0-pre"; export enum PointShape { Circle = 'circle', diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts index 6ecb17f191b..3cb2c8aab5b 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts @@ -259,6 +259,8 @@ export interface FieldConfigSource { defaults: FieldConfig; // Overrides are the options applied to specific fields overriding the defaults. overrides: { + // Describes config override rules created when interacting with Grafana. + __systemRef?: string; matcher: MatcherConfig; properties: DynamicConfigValue[]; }[]; @@ -457,7 +459,8 @@ export type ThresholdsMode = "absolute" | "percentage"; export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); export interface Threshold { - value: number; + // Value null means -Infinity + value: number | null; color: string; } diff --git a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts index 35ccf3cdb1f..00e9111453a 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts @@ -162,7 +162,7 @@ export interface PanelQuerySpec { export const defaultPanelQuerySpec = (): PanelQuerySpec => ({ query: defaultDataQueryKind(), - refId: "", + refId: "A", hidden: false, }); @@ -266,6 +266,8 @@ export interface FieldConfigSource { defaults: FieldConfig; // Overrides are the options applied to specific fields overriding the defaults. overrides: { + // Describes config override rules created when interacting with Grafana. + __systemRef?: string; matcher: MatcherConfig; properties: DynamicConfigValue[]; }[]; @@ -464,7 +466,8 @@ export type ThresholdsMode = "absolute" | "percentage"; export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); export interface Threshold { - value: number; + // Value null means -Infinity + value: number | null; color: string; } @@ -1282,7 +1285,7 @@ export interface IntervalVariableSpec { auto: boolean; auto_min: string; auto_count: number; - refresh: VariableRefresh; + refresh: "onTimeRangeChanged"; label?: string; hide: VariableHide; skipUrlSync: boolean; @@ -1297,7 +1300,7 @@ export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({ auto: false, auto_min: "", auto_count: 0, - refresh: "never", + refresh: "onTimeRangeChanged", hide: "dontHide", skipUrlSync: false, }); diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index db36dfec348..321cfccfe7b 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "@grafana/sql", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git", @@ -16,12 +16,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.3.0-pre", - "@grafana/e2e-selectors": "12.3.0-pre", - "@grafana/i18n": "12.3.0-pre", + "@grafana/data": "12.4.0-pre", + "@grafana/e2e-selectors": "12.4.0-pre", + "@grafana/i18n": "12.4.0-pre", "@grafana/plugin-ui": "^0.10.10", - "@grafana/runtime": "12.3.0-pre", - "@grafana/ui": "12.3.0-pre", + "@grafana/runtime": "12.4.0-pre", + "@grafana/ui": "12.4.0-pre", "@react-awesome-query-builder/ui": "6.6.15", "immutable": "5.1.4", "lodash": "4.17.21", diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index ecd8980173a..a8b69f8d665 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/test-utils", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "private": true, "description": "Grafana test utils & Mock API", "keywords": [ diff --git a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v1beta1/handlers.ts b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v1beta1/handlers.ts index 0c9c364780d..251ca44e766 100644 --- a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v1beta1/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v1beta1/handlers.ts @@ -27,6 +27,7 @@ const dashboardToAppPlatform = (dashboard: (typeof mockTree)[number]['item']) => }, status: {}, // TODO: Eventually add access properties, as required by tests + access: {}, }; }; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 6c96682758b..011a26fb86d 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "12.3.0-pre", + "version": "12.4.0-pre", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -67,11 +67,11 @@ "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", "@floating-ui/react": "0.27.16", - "@grafana/data": "12.3.0-pre", - "@grafana/e2e-selectors": "12.3.0-pre", + "@grafana/data": "12.4.0-pre", + "@grafana/e2e-selectors": "12.4.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/i18n": "12.3.0-pre", - "@grafana/schema": "12.3.0-pre", + "@grafana/i18n": "12.4.0-pre", + "@grafana/schema": "12.4.0-pre", "@hello-pangea/dnd": "18.0.1", "@monaco-editor/react": "4.7.0", "@popperjs/core": "2.11.8", diff --git a/packages/grafana-ui/src/components/Button/Button.test.tsx b/packages/grafana-ui/src/components/Button/Button.test.tsx index 52589d67196..b78b9aafe5d 100644 --- a/packages/grafana-ui/src/components/Button/Button.test.tsx +++ b/packages/grafana-ui/src/components/Button/Button.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { Button } from './Button'; +import { Button, LinkButton } from './Button'; const setup = (jsx: JSX.Element) => { return { @@ -64,3 +64,37 @@ describe('Button', () => { expect(screen.getByRole('button', { name: 'Aria label' })).toBeInTheDocument(); }); }); + +describe('LinkButton', () => { + it('should place the icon on the right when iconPlacement is "right"', () => { + setup( + + Click me + + ); + + const link = screen.getByRole('link'); + const icon = screen.getByTitle(''); + const textSpan = link.querySelector('span'); + + // Assert that the text span comes before the icon in the DOM + expect(link.childNodes[0]).toBe(textSpan); + expect(link.childNodes[1]).toBe(icon); + }); + + it('should place the icon on the left when iconPlacement is "left"', () => { + setup( + + Click me + + ); + + const link = screen.getByRole('link'); + const icon = screen.getByTitle(''); + const textSpan = link.querySelector('span'); + + // Assert that the icon comes before the text span in the DOM + expect(link.childNodes[0]).toBe(icon); + expect(link.childNodes[1]).toBe(textSpan); + }); +}); diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index 9e1d1c4a1a9..d8e6e54d3fa 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -142,6 +142,7 @@ export const LinkButton = React.forwardRef( size = 'md', fill = 'solid', icon, + iconPlacement = 'left', fullWidth, children, className, @@ -174,6 +175,8 @@ export const LinkButton = React.forwardRef( className ); + const iconComponent = icon && ; + // When using tooltip, ref is forwarded to Tooltip component instead for https://github.com/grafana/grafana/issues/65632 const button = ( ( ref={tooltip ? undefined : ref} aria-label={ariaLabel ?? (!children && typeof tooltip === 'string' ? tooltip : undefined)} > - + {iconPlacement === 'left' && iconComponent} {children && {children}} + {iconPlacement === 'right' && iconComponent} ); diff --git a/packages/grafana-ui/src/components/Card/CardContainer.tsx b/packages/grafana-ui/src/components/Card/CardContainer.tsx index 270ac37931c..f970356ec80 100644 --- a/packages/grafana-ui/src/components/Card/CardContainer.tsx +++ b/packages/grafana-ui/src/components/Card/CardContainer.tsx @@ -93,7 +93,7 @@ export const getCardContainerStyles = ( display: 'grid', position: 'relative', gridTemplateColumns: 'auto 1fr auto', - gridTemplateRows: '1fr auto auto auto', + gridTemplateRows: 'auto auto 1fr auto', gridAutoColumns: '1fr', gridAutoFlow: 'row', gridTemplateAreas: ` diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx index b3794bf3fa9..581cb6223bf 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx @@ -1,7 +1,7 @@ import { action } from '@storybook/addon-actions'; import { useArgs, useEffect, useState } from '@storybook/preview-api'; import type { Meta, StoryFn, StoryObj } from '@storybook/react'; -import { ComponentProps } from 'react'; +import { ComponentProps, useId } from 'react'; import { Field } from '../Forms/Field'; @@ -17,8 +17,6 @@ const meta: Meta = { docs: { page: mdx, }, - // TODO fix a11y issue in story and remove this - a11y: { test: 'off' }, }, }; @@ -48,22 +46,47 @@ type ManyOptionsArgs = storyArgs & { numberOfOptions?: number }; type Story = StoryObj; -export const Basic: Story = { - args: commonArgs, - render: (args) => { - const [{ value }, setArgs] = useArgs(); +const BasicStory: StoryFn = (args) => { + const [{ value }, setArgs] = useArgs(); + const comboboxId = useId(); - return ( + return ( + { onChangeAction(val); setArgs({ value: val }); }} /> - ); - }, + + ); +}; + +export const Basic: Story = { + args: commonArgs, + render: BasicStory, +}; + +const WithInfoOptionStory: StoryFn = (args) => { + const [{ value }, setArgs] = useArgs(); + const comboboxId = useId(); + + return ( + + { + onChangeAction(val); + setArgs({ value: val }); + }} + /> + + ); }; export const WithInfoOption: Story = { @@ -75,44 +98,37 @@ export const WithInfoOption: Story = { { label: 'Can’t find your country? Select “Other” or contact an admin', value: '__INFO__', infoOption: true }, ], }, - render: (args) => { - const [{ value }, setArgs] = useArgs(); - - return ( - { - onChangeAction(val); - setArgs({ value: val }); - }} - /> - ); - }, + render: WithInfoOptionStory, }; -export const AutoSize: Story = { - args: { ...commonArgs, width: 'auto', minWidth: 20 }, - render: (args) => { - const [{ value }, setArgs] = useArgs(); +const AutoSizeStory: StoryFn = (args) => { + const [{ value }, setArgs] = useArgs(); + const comboboxId = useId(); - return ( + return ( + { action('onChange')(val); setArgs({ value: val }); }} /> - ); - }, + + ); +}; + +export const AutoSize: Story = { + args: { ...commonArgs, width: 'auto', minWidth: 20 }, + render: AutoSizeStory, }; const ManyOptionsStory: StoryFn = ({ numberOfOptions = 1e4, ...args }) => { const [dynamicArgs, setArgs] = useArgs(); - const [options, setOptions] = useState([]); + const comboboxId = useId(); useEffect(() => { setTimeout(async () => { @@ -123,15 +139,18 @@ const ManyOptionsStory: StoryFn = ({ numberOfOptions = 1e4, ... const { onChange, ...rest } = args; return ( - { - setArgs({ value: opts }); - onChangeAction(opts); - }} - /> + + { + setArgs({ value: opts }); + onChangeAction(opts); + }} + /> + ); }; @@ -146,8 +165,8 @@ export const ManyOptions: StoryObj = { const ManyOptionsGroupedStory: StoryFn = ({ numberOfOptions = 1e5, ...args }) => { const [dynamicArgs, setArgs] = useArgs(); - const [options, setOptions] = useState([]); + const comboboxId = useId(); useEffect(() => { setTimeout(async () => { @@ -157,15 +176,18 @@ const ManyOptionsGroupedStory: StoryFn = ({ numberOfOptions = 1 }, [numberOfOptions]); const { onChange, ...rest } = args; return ( - { - setArgs({ value: opts }); - onChangeAction(opts); - }} - /> + + { + setArgs({ value: opts }); + onChangeAction(opts); + }} + /> + ); }; @@ -183,6 +205,28 @@ function loadOptionsWithLabels(inputValue: string) { return fakeSearchAPI(`http://example.com/search?errorOnQuery=break&query=${inputValue}`); } +const AsyncOptionsWithLabelsStory: StoryFn = (args) => { + const [dynamicArgs, setArgs] = useArgs(); + const comboboxId = useId(); + + return ( + + { + onChangeAction(val); + setArgs({ value: val }); + }} + /> + + ); +}; + export const AsyncOptionsWithLabels: Story = { name: 'Async - options returns labels', args: { @@ -190,25 +234,7 @@ export const AsyncOptionsWithLabels: Story = { value: [{ label: 'Option 69', value: '69' }], placeholder: 'Select an option', }, - render: (args) => { - const [dynamicArgs, setArgs] = useArgs(); - - return ( - - { - onChangeAction(val); - setArgs({ value: val }); - }} - /> - - ); - }, + render: AsyncOptionsWithLabelsStory, }; function loadOptionsOnlyValues(inputValue: string) { @@ -218,6 +244,28 @@ function loadOptionsOnlyValues(inputValue: string) { ); } +const AsyncOptionsWithOnlyValuesStory: StoryFn = (args) => { + const [dynamicArgs, setArgs] = useArgs(); + const comboboxId = useId(); + + return ( + + { + onChangeAction(val); + setArgs({ value: val }); + }} + /> + + ); +}; + export const AsyncOptionsWithOnlyValues: Story = { name: 'Async - options returns only values', args: { @@ -225,23 +273,5 @@ export const AsyncOptionsWithOnlyValues: Story = { value: [{ value: 'Option 69' }], placeholder: 'Select an option', }, - render: (args) => { - const [dynamicArgs, setArgs] = useArgs(); - - return ( - - { - onChangeAction(val); - setArgs({ value: val }); - }} - /> - - ); - }, + render: AsyncOptionsWithOnlyValuesStory, }; diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx index 3a1e2856a68..82759befffb 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx @@ -57,6 +57,7 @@ export const MultiCombobox = (props: MultiComboboxPro 'data-testid': dataTestId, portalContainer, prefixIcon, + id, } = props; const styles = useStyles2(getComboboxStyles); @@ -152,17 +153,10 @@ export const MultiCombobox = (props: MultiComboboxPro }, }); - const { - getToggleButtonProps, - //getLabelProps, - isOpen, - highlightedIndex, - getMenuProps, - getInputProps, - getItemProps, - } = useCombobox({ + const { isOpen, highlightedIndex, getMenuProps, getInputProps, getItemProps } = useCombobox({ items: options, itemToString, + inputId: id, inputValue, selectedItem: null, stateReducer: (state, actionAndChanges) => { @@ -325,7 +319,7 @@ export const MultiCombobox = (props: MultiComboboxPro })} /> -
+
{isClearable && selectedItems.length > 0 && ( extends Pick, 'options' | 'onOptionsChange'> {} @@ -17,31 +19,36 @@ export function AlertingSettings({ options, onOptionsC

Alerting

-
-
-
- - - onOptionsChange({ - ...options, - jsonData: { ...options.jsonData, manageAlerts: event!.currentTarget.checked }, - }) - } - /> - -
-
-
+ + + + + + + onOptionsChange({ + ...options, + jsonData: { ...options.jsonData, manageAlerts: event!.currentTarget.checked }, + }) + } + /> + + + + + ); } diff --git a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx index 49e2abf2a2a..d7d94b186c2 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx @@ -9,6 +9,8 @@ import { useStyles2 } from '../../themes/ThemeContext'; import { Button } from '../Button/Button'; import { FormField } from '../FormField/FormField'; import { Icon } from '../Icon/Icon'; +import { Box } from '../Layout/Box/Box'; +import { Stack } from '../Layout/Stack/Stack'; import { SecretFormField } from '../SecretFormField/SecretFormField'; export interface CustomHeader { @@ -199,12 +201,14 @@ export class CustomHeadersSettings extends PureComponent { const { dataSourceConfig } = this.props; return ( -
-
-
- Custom HTTP Headers -
-
+ + + +
+ Custom HTTP Headers +
+
+
{headers.map((header, i) => ( { ))}
{!dataSourceConfig.readOnly && ( -
- -
+ + + + + )} -
+ ); } } diff --git a/packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx index 6004fff49e6..d8708ddb470 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx @@ -3,6 +3,8 @@ import { t, Trans } from '@grafana/i18n'; import { InlineSwitch } from '../../components/Switch/Switch'; import { InlineField } from '../Forms/InlineField'; +import { Box } from '../Layout/Box/Box'; +import { Stack } from '../Layout/Stack/Stack'; export interface Props extends Pick, 'options' | 'onOptionsChange'> {} @@ -20,30 +22,32 @@ export function SecureSocksProxySettings({

Secure Socks Proxy

-
-
-
- - - onOptionsChange({ - ...options, - jsonData: { ...options.jsonData, enableSecureSocksProxy: event!.currentTarget.checked }, - }) - } - /> - -
-
-
+ + + + + + + onOptionsChange({ + ...options, + jsonData: { ...options.jsonData, enableSecureSocksProxy: event!.currentTarget.checked }, + }) + } + /> + + + + +
); } diff --git a/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx index b5a1537edd8..fbd20c5c533 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx @@ -1,4 +1,3 @@ -import { css, cx } from '@emotion/css'; import * as React from 'react'; import { KeyValue } from '@grafana/data'; @@ -6,6 +5,8 @@ import { t, Trans } from '@grafana/i18n'; import { FormField } from '../FormField/FormField'; import { Icon } from '../Icon/Icon'; +import { Box } from '../Layout/Box/Box'; +import { Stack } from '../Layout/Stack/Stack'; import { Tooltip } from '../Tooltip/Tooltip'; import { CertificationKey } from './CertificationKey'; @@ -53,29 +54,24 @@ export const TLSAuthSettings = ({ dataSourceConfig, onChange }: HttpSettingsBase const privateKeyBeginsWith = '-----BEGIN RSA PRIVATE KEY-----'; return ( -
-
-
- TLS/SSL Auth Details -
- - - -
+ + + +
+ TLS/SSL Auth Details +
+ + + +
+
{dataSourceConfig.jsonData.tlsAuthWithCACert && ( -
- -
+ + + + + )}
-
+ ); }; diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx index 737bd59943f..31013010033 100644 --- a/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.mdx @@ -1,6 +1,6 @@ # InlineFieldRow -Used to align multiple `InlineField` components in one row. The row will wrap if the width of the children exceeds its own. Equivalent to the div with `gf-form-inline` class name. +Used to align multiple `InlineField` components in one row. The row will wrap if the width of the children exceeds its own. Multiple `InlineFieldRow`s vertically stack on each other. ### Usage diff --git a/packages/grafana-ui/src/components/Modal/ModalsContext.tsx b/packages/grafana-ui/src/components/Modal/ModalsContext.tsx index 4a540716fee..734e3fe620b 100644 --- a/packages/grafana-ui/src/components/Modal/ModalsContext.tsx +++ b/packages/grafana-ui/src/components/Modal/ModalsContext.tsx @@ -17,10 +17,6 @@ export const ModalsContext = React.createContext({ interface ModalsProviderProps { children: React.ReactNode; - /** Set default component to render as modal. Useful when rendering modals from Angular */ - component?: React.ComponentType | null; - /** Set default component props. Useful when rendering modals from Angular */ - props?: any; } /** @@ -31,8 +27,8 @@ export class ModalsProvider extends Component = { docs: { page: mdx, }, - // TODO fix a11y issue in story and remove this - a11y: { test: 'off' }, }, }; @@ -46,7 +44,7 @@ function getContentStyle(): CSSProperties { }; } -function renderPanel(name: string, overrides?: Partial) { +function renderPanel(content: string, overrides?: Partial) { const props: PanelChromeProps = { width: PANEL_WIDTH, height: PANEL_HEIGHT, @@ -60,7 +58,7 @@ function renderPanel(name: string, overrides?: Partial) { return ( {(innerWidth: number, innerHeight: number) => { - return
{name}
; + return
{content}
; }}
); @@ -133,70 +131,74 @@ export const Examples = () => {
- {renderPanel('Has statusMessage', { - title: 'Default title', + {renderPanel('Content', { + title: 'Panel with statusMessage', statusMessage: 'Error text', statusMessageOnClick: action('ErrorIndicator: onClick fired'), })} - {renderPanel('No padding, has statusMessage', { + {renderPanel('Content', { padding: 'none', - title: 'Default title', + title: 'Panel with statusMessage and no padding', statusMessage: 'Error text', statusMessageOnClick: action('ErrorIndicator: onClick fired'), })} - {renderPanel('No title, loadingState is Error, no statusMessage', { + {renderPanel('Content', { loadingState: LoadingState.Error, + title: 'No title, loadingState is Error, no statusMessage', })} - {renderPanel('loadingState is Streaming', { - title: 'Default title', + {renderPanel('Content', { + title: 'loadingState is Streaming', loadingState: LoadingState.Streaming, })} - {renderPanel('loadingState is Loading', { - title: 'Default title', + {renderPanel('Content', { + title: 'loadingState is Loading', loadingState: LoadingState.Loading, })} {renderPanel('Default panel: no non-required props')} - {renderPanel('No padding', { + {renderPanel('Content', { padding: 'none', + title: 'No padding', })} - {renderPanel('Very long title', { + {renderPanel('Content', { title: 'Very long title that should get ellipsis when there is no more space', })} - {renderPanel('No title, streaming loadingState', { + {renderPanel('Content', { + title: 'No title, streaming loadingState', loadingState: LoadingState.Streaming, })} - {renderPanel('Error status, menu', { - title: 'Default title', + {renderPanel('Content', { + title: 'Error status, menu', menu, statusMessage: 'Error text', statusMessageOnClick: action('ErrorIndicator: onClick fired'), })} - {renderPanel('No padding; has statusMessage, menu', { + {renderPanel('Content', { padding: 'none', - title: 'Default title', + title: 'No padding; has statusMessage, menu', menu, statusMessage: 'Error text', statusMessageOnClick: action('ErrorIndicator: onClick fired'), })} - {renderPanel('No title, loadingState is Error, no statusMessage, menu', { + {renderPanel('Content', { + title: 'No title, loadingState is Error, no statusMessage, menu', menu, loadingState: LoadingState.Error, })} - {renderPanel('loadingState is Streaming, menu', { - title: 'Default title', + {renderPanel('Content', { + title: 'loadingState is Streaming, menu', menu, loadingState: LoadingState.Streaming, })} - {renderPanel('loadingState is Loading, menu', { - title: 'Default title', + {renderPanel('Content', { + title: 'loadingState is Loading, menu', menu, loadingState: LoadingState.Loading, })} - {renderPanel('No padding, deprecated loading indicator', { + {renderPanel('Content', { padding: 'none', - title: 'Default title', + title: 'No padding, deprecated loading indicator', leftItems: [ { />, ], })} - {renderPanel('Display mode = transparent', { - title: 'Default title', + {renderPanel('Content', { + title: 'Display mode = transparent', displayMode: 'transparent', menu, })} - {renderPanel('Actions with button no menu', { + {renderPanel('Content', { title: 'Actions with button no menu', actions: ( ), })} - {renderPanel('Panel with two actions', { - title: 'I have two buttons', + {renderPanel('Content', { + title: 'Panel with two actions', actions: [ ), +})); + +jest.mock('kbar', () => ({ + ...jest.requireActual('kbar'), + KBarPortal: jest.fn().mockImplementation(({ children }) =>
{children}
), + KBarAnimator: jest.fn().mockImplementation(({ children }) =>
{children}
), +})); + +const setup = () => { + return render( + + + + ); +}; + +describe('CommandPalette', () => { + it('should render empty state with AI Assistant button when no results and assistant is available', async () => { + // Mock assistant being available + (useAssistant as jest.Mock).mockReturnValue({ isAvailable: true }); + setup(); + + // Check if empty state message is rendered + expect(await screen.findByText('No results found')).toBeInTheDocument(); + // Check if AI Assistant button is rendered with correct props + expect(screen.getByRole('button', { name: 'Search with Grafana Assistant' })).toBeInTheDocument(); + }); + + it('should render empty state without AI Assistant button when assistant is not available', async () => { + // Mock assistant being unavailable + (useAssistant as jest.Mock).mockReturnValue({ isAvailable: false }); + setup(); + + // Check if empty state message is rendered + expect(await screen.findByText('No results found')).toBeInTheDocument(); + // Check that AI Assistant button is not rendered + expect(screen.queryByRole('button', { name: 'Search with Grafana Assistant' })).not.toBeInTheDocument(); + }); +}); diff --git a/public/app/features/commandPalette/CommandPalette.tsx b/public/app/features/commandPalette/CommandPalette.tsx index 1bd1634546c..2c223f4f1a6 100644 --- a/public/app/features/commandPalette/CommandPalette.tsx +++ b/public/app/features/commandPalette/CommandPalette.tsx @@ -5,6 +5,7 @@ import { useOverlay } from '@react-aria/overlays'; import { KBarAnimator, KBarPortal, KBarPositioner, VisualState, useKBar, ActionImpl } from 'kbar'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import { OpenAssistantButton, useAssistant } from '@grafana/assistant'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; @@ -88,7 +89,11 @@ function CommandPaletteContents() {
{scopesRow ?
{scopesRow}
: null}
- +
@@ -131,10 +136,13 @@ function AncestorBreadcrumbs() { interface RenderResultsProps { isFetchingSearchResults: boolean; searchResults: CommandPaletteAction[]; + searchQuery: string; } -const RenderResults = ({ isFetchingSearchResults, searchResults }: RenderResultsProps) => { +const RenderResults = ({ isFetchingSearchResults, searchResults, searchQuery }: RenderResultsProps) => { const { results: kbarResults, rootActionId } = useMatches(); + const { query } = useKBar(); + const { isAvailable: isAssistantAvailable } = useAssistant(); const lateralSpace = getCommandPalettePosition(); const styles = useStyles2(getSearchStyles, lateralSpace); @@ -176,11 +184,16 @@ const RenderResults = ({ isFetchingSearchResults, searchResults }: RenderResults }, [showEmptyState]); return showEmptyState ? ( - + + {isAssistantAvailable && ( + + )} + ) : ( - -

- Correlations are disabled -

- To enable Correlations, add it in the Grafana config: -
-
-            {`[feature_toggles]
-correlations = true
-`}
-          
-
-
- - ); -} diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index e17f7e36cff..e7c854fc815 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -5,6 +5,7 @@ import { sceneGraph } from '@grafana/scenes'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { GetRepositoryFilesWithPathApiResponse, provisioningAPIv0alpha1 } from 'app/api/clients/provisioning/v0alpha1'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; +import { contextSrv } from 'app/core/services/context_srv'; import { getMessageFromError, getMessageIdFromError, getStatusFromError } from 'app/core/utils/errors'; import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import { @@ -235,6 +236,7 @@ abstract class DashboardScenePageStateManagerBase private processDashboardFromProvisioning( repo: string, path: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any dryRun: any, provisioningPreview: ProvisioningPreview ) { @@ -474,13 +476,40 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag throw new Error('Snapshot not found'); } - private async loadTemplateDashboard(): Promise { + private buildDashboardDTOFromInterpolated(interpolatedDashboard: DashboardDataDTO): DashboardDTO { + return { + dashboard: { + ...interpolatedDashboard, + uid: '', + version: 0, + id: null, + }, + meta: { + canSave: contextSrv.hasEditPermissionInFolders, + canEdit: contextSrv.hasEditPermissionInFolders, + canStar: false, + canShare: false, + canDelete: false, + isNew: true, + folderUid: '', + }, + }; + } + + private async loadSuggestedDashboard(): Promise { // Extract template parameters from URL const searchParams = new URLSearchParams(window.location.search); const datasource = searchParams.get('datasource'); + const gnetId = searchParams.get('gnetId'); const pluginId = searchParams.get('pluginId'); const path = searchParams.get('path'); + // Check if this is a community dashboard (has gnetId) or plugin dashboard + if (gnetId) { + return this.loadCommunityTemplateDashboard(gnetId); + } + + // Original plugin dashboard flow if (!datasource || !pluginId || !path) { throw new Error('Missing required parameters for template dashboard'); } @@ -505,24 +534,41 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag }; const interpolatedDashboard = await getBackendSrv().post('/api/dashboards/interpolate', data); + return this.buildDashboardDTOFromInterpolated(interpolatedDashboard); + } - return { - dashboard: { - ...interpolatedDashboard, - uid: '', - version: 0, - id: null, - }, - meta: { - canSave: true, - canEdit: true, - canStar: false, - canShare: false, - canDelete: false, - isNew: true, - folderUid: '', - }, + private async loadCommunityTemplateDashboard(gnetId: string): Promise { + // Extract mappings from URL params + const location = locationService.getLocation(); + const searchParams = new URLSearchParams(location.search); + const mappingsJson = searchParams.get('mappings'); + + if (!mappingsJson) { + throw new Error('Missing mappings parameter for community dashboard'); + } + + let mappings; + try { + mappings = JSON.parse(mappingsJson); + } catch (err) { + throw new Error('Invalid mappings parameter: ' + err); + } + + // Fetch the community dashboard from grafana.com + const gnetDashboard = await getBackendSrv().get(`/api/gnet/dashboards/${gnetId}`); + + // The dashboard JSON is in the 'json' property + const dashboardJson = gnetDashboard.json; + + // Call interpolate endpoint with the dashboard JSON and mappings + const data = { + dashboard: dashboardJson, + overwrite: true, + inputs: mappings, }; + + const interpolatedDashboard = await getBackendSrv().post('/api/dashboards/interpolate', data); + return this.buildDashboardDTOFromInterpolated(interpolatedDashboard); } public async fetchDashboard({ @@ -559,7 +605,7 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag rsp = await buildNewDashboardSaveModel(urlFolderUid); break; case DashboardRoutes.Template: - rsp = await this.loadTemplateDashboard(); + rsp = await this.loadSuggestedDashboard(); break; case DashboardRoutes.Provisioning: return this.loadProvisioningDashboard(slug || '', uid); @@ -885,7 +931,7 @@ export class UnifiedDashboardScenePageStateManager extends DashboardScenePageSta this.v1Manager = new DashboardScenePageStateManager(initialState); this.v2Manager = new DashboardScenePageStateManagerV2(initialState); - this.activeManager = this.v1Manager; + this.activeManager = config.featureToggles.dashboardNewLayouts ? this.v2Manager : this.v1Manager; } private async withVersionHandling( diff --git a/public/app/features/dashboard-scene/pages/utils.test.ts b/public/app/features/dashboard-scene/pages/utils.test.ts index 416181dee15..0edcf14bea5 100644 --- a/public/app/features/dashboard-scene/pages/utils.test.ts +++ b/public/app/features/dashboard-scene/pages/utils.test.ts @@ -1,4 +1,4 @@ -import { backendSrv } from 'app/core/services/backend_srv'; +import * as folderHooks from 'app/api/clients/folder/v1beta1/hooks'; import { configureStore } from 'app/store/configureStore'; import { updateNavModel } from './utils'; @@ -7,7 +7,7 @@ describe('utils', () => { it('Should update nav model', async () => { const reduxStore = configureStore(); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue({ + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockResolvedValue({ id: 1, uid: 'new-folder', title: 'NewFolder', diff --git a/public/app/features/dashboard-scene/pages/utils.ts b/public/app/features/dashboard-scene/pages/utils.ts index d540e8fbdb5..6e786123a87 100644 --- a/public/app/features/dashboard-scene/pages/utils.ts +++ b/public/app/features/dashboard-scene/pages/utils.ts @@ -1,13 +1,13 @@ import { UrlQueryMap, getTimeZone, getDefaultTimeRange, dateMath } from '@grafana/data'; import { locationService } from '@grafana/runtime'; +import { getFolderByUidFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { updateNavIndex } from 'app/core/actions'; -import { backendSrv } from 'app/core/services/backend_srv'; import { buildNavModel } from 'app/features/folders/state/navModel'; import { store } from 'app/store/store'; export async function updateNavModel(folderUid: string) { try { - const folder = await backendSrv.getFolderByUid(folderUid); + const folder = await getFolderByUidFacade(folderUid); store.dispatch(updateNavIndex(buildNavModel(folder))); } catch (err) { console.warn('Error fetching parent folder', folderUid, 'for dashboard', err); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx index 06573cc06ef..15e323aa8a9 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx @@ -4,7 +4,6 @@ import { byTestId } from 'testing-library-selector'; import { PromOptions } from '@grafana/prometheus'; import { config, locationService, setPluginLinksHook } from '@grafana/runtime'; -import { backendSrv } from 'app/core/services/backend_srv'; import * as ruler from 'app/features/alerting/unified/api/ruler'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import * as alertingAbilities from 'app/features/alerting/unified/hooks/useAbilities'; @@ -12,7 +11,6 @@ import { mockAlertRuleApi, setupMswServer } from 'app/features/alerting/unified/ import { grantUserPermissions, mockDataSource, - mockFolder, mockPromAlert, mockPromAlertingRule, mockRulerAlertingRule, @@ -190,7 +188,6 @@ describe('PanelAlertTabContent', () => { AccessControlAction.AlertingRuleExternalWrite, ]); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue(mockFolder()); setupDataSources(...Object.values(dataSources)); mocks.rulerBuilderMock.mockReturnValue({ diff --git a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx index 8166b82d071..a57aac9d427 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx @@ -138,10 +138,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ padding: theme.spacing(2, 1), height: '100%', gap: theme.spacing(2), - border: `1px solid ${theme.colors.border.weak}`, - borderRight: 'none', - borderBottom: 'none', - borderTopLeftRadius: theme.shape.radius.default, }), searchRow: css({ display: 'flex', diff --git a/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx b/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx index d74717c9285..03ac5f0ea88 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx @@ -88,7 +88,7 @@ function DashboardControlsMenu({ variables, links, annotationLayers, dashboardUI > {/* Variables */} {variables.map((variable, index) => ( -
0 })} key={variable.state.key}> +
0 })} key={variable.state.key}>
))} @@ -96,7 +96,7 @@ function DashboardControlsMenu({ variables, links, annotationLayers, dashboardUI {/* Annotation layers */} {annotationLayers.length > 0 && annotationLayers.map((layer, index) => ( -
0 && styles.variableItem)} key={layer.state.key}> +
0 || index > 0 })} key={layer.state.key}>
))} @@ -131,7 +131,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ marginTop: theme.spacing(2), padding: theme.spacing(0, 0.5), }), - variableItem: css({ + menuItem: css({ marginTop: theme.spacing(2), }), }); diff --git a/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx b/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx index ff46c11cc0e..94484509991 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx @@ -1,13 +1,21 @@ import { PointerEvent as ReactPointerEvent } from 'react'; -import { sceneGraph, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { logWarning } from '@grafana/runtime'; +import { + sceneGraph, + SceneObjectBase, + SceneObjectRef, + SceneObjectState, + VizPanel, + SceneGridItemLike, +} from '@grafana/scenes'; import { createPointerDistance } from '@grafana/ui'; import { DashboardScene } from './DashboardScene'; import { DashboardDropTarget, isDashboardDropTarget } from './types/DashboardDropTarget'; interface DashboardLayoutOrchestratorState extends SceneObjectState { - draggingPanel?: SceneObjectRef; + draggingGridItem?: SceneObjectRef; } export class DashboardLayoutOrchestrator extends SceneObjectBase { @@ -32,11 +40,11 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase { - this._sourceDropTarget?.draggedPanelOutside?.(panel!); - this._lastDropTarget?.draggedPanelInside?.(panel!); + if (gridItem) { + // Always use grid item dragging + this._sourceDropTarget?.draggedGridItemOutside?.(gridItem); + this._lastDropTarget?.draggedGridItemInside?.(gridItem); + } else { + const warningMessage = 'No grid item to drag'; + console.warn(warningMessage); + logWarning(warningMessage); + } }); } document.body.removeEventListener('pointermove', this._onPointerMove); document.body.removeEventListener('pointerup', this._stopDraggingSync); - this.setState({ draggingPanel: undefined }); + this.setState({ draggingGridItem: undefined }); } private _onPointerMove(evt: PointerEvent) { - if (!this._isSelectedObject && this.state.draggingPanel && this._pointerDistance.check(evt)) { + if (!this._isSelectedObject && this.state.draggingGridItem && this._pointerDistance.check(evt)) { this._isSelectedObject = true; - const panel = this.state.draggingPanel?.resolve(); - this._getDashboard().state.editPane.selectObject(panel, panel.state.key!, { force: true, multi: false }); + const gridItem = this.state.draggingGridItem?.resolve(); + if (gridItem && 'state' in gridItem && 'body' in gridItem.state && gridItem.state.body instanceof VizPanel) { + const panel = gridItem.state.body; + this._getDashboard().state.editPane.selectObject(panel, panel.state.key!, { force: true, multi: false }); + } } const dropTarget = this._getDropTargetUnderMouse(evt) ?? this._sourceDropTarget; diff --git a/public/app/features/dashboard-scene/scene/DataLayerControl.tsx b/public/app/features/dashboard-scene/scene/DataLayerControl.tsx index 8bc3f2f5c5b..6318fa248a6 100644 --- a/public/app/features/dashboard-scene/scene/DataLayerControl.tsx +++ b/public/app/features/dashboard-scene/scene/DataLayerControl.tsx @@ -69,6 +69,8 @@ const getStyles = (theme: GrafanaTheme2) => ({ '& > div': { border: 'none', background: 'transparent', + paddingRight: theme.spacing(0.5), + height: theme.spacing(2), '&:hover': { border: 'none', background: 'transparent', @@ -76,6 +78,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ }, }), menuLabel: css({ - marginTop: theme.spacing(0.5), + marginTop: 0, + marginBottom: 0, }), }); diff --git a/public/app/features/dashboard-scene/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index 470b20fea23..eba51280522 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -27,11 +27,7 @@ export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { .map((variable) => ( ))} - {config.featureToggles.dashboardNewLayouts ? ( -
- -
- ) : null} + {config.featureToggles.dashboardNewLayouts ? : null} ); } @@ -60,10 +56,16 @@ export function VariableValueSelectWrapper({ variable, inMenu }: VariableSelectP } // Ignore click if it's inside the value control - if (evt.target instanceof Element && !evt.target.closest(`label`)) { - // Prevent clearing selection when clicking inside value - evt.stopPropagation(); - return; + if (evt.target instanceof Element) { + // multi variable options contain label element so we need a more specific + // condition to target variable label to prevent edit pane selection on option click + const forAttribute = evt.target.closest('label[for]')?.getAttribute('for'); + + if (!(forAttribute === `var-${variable.state.key || ''}`)) { + // Prevent clearing selection when clicking inside value + evt.stopPropagation(); + return; + } } if (isSelectable && onSelect) { @@ -171,10 +173,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ marginTop: 0, marginBottom: 0, }), - labelWrapper: css({ - display: 'flex', - alignItems: 'center', - }), labelSelectable: css({ cursor: 'pointer', }), diff --git a/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx b/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx index 9d20b740030..8bdabd397d1 100644 --- a/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx @@ -9,7 +9,7 @@ import { DashboardInteractions } from '../utils/interactions'; import { DashboardScene } from './DashboardScene'; export function AddVariableButton({ dashboard }: { dashboard: DashboardScene }) { - const { isEditing } = dashboard.useState(); + const { editview, editPanel, isEditing, viewPanel } = dashboard.useState(); const handlePointerDown: PointerEventHandler = useCallback( (evt) => { @@ -20,13 +20,20 @@ export function AddVariableButton({ dashboard }: { dashboard: DashboardScene }) [dashboard] ); - if (!isEditing) { + // Hide the button if: + // - the dashboard is not in edit mode + // - the dashboard is in an edit view mode + // - the dashboard is in a view panel mode + // - the dashboard is in an edit panel mode + if (!isEditing || !!editview || !!viewPanel || !!editPanel) { return null; } return ( - +
+ +
); } diff --git a/public/app/features/dashboard-scene/scene/export/exporters.test.ts b/public/app/features/dashboard-scene/scene/export/exporters.test.ts index 38cc91bf462..f91f1563abc 100644 --- a/public/app/features/dashboard-scene/scene/export/exporters.test.ts +++ b/public/app/features/dashboard-scene/scene/export/exporters.test.ts @@ -293,6 +293,65 @@ describe('dashboard exporter v1', () => { expect(exported.panels[0].targets[0].datasource).toEqual({ uid: '${DS_OTHER}', type: 'other' }); }); + it('should not attempt to templateize datasource variable ref if they have already been templateized', async () => { + const dashboard: Dashboard = { + title: 'My dashboard', + revision: 1, + editable: false, + graphTooltip: DashboardCursorSync.Off, + schemaVersion: 1, + timepicker: { hidden: true }, + timezone: '', + panels: [ + { + id: 1, + type: 'timeseries', + title: 'My panel title', + gridPos: { x: 0, y: 0, w: 1, h: 1 }, + datasource: { + type: 'prometheus', + uid: '${ds_var}', + }, + }, + ], + templating: { + list: [ + { + current: { + selected: false, + text: 'my-prometheus-datasource', + value: 'my-prometheus-datasource-uid', + }, + hide: 0, + includeAll: false, + multi: false, + name: 'ds_var', + options: [], + query: 'prometheus', + refresh: 1, + regex: '', + skipUrlSync: false, + type: 'datasource', + }, + // query variable here uses the datasource variable that has already been templateized + { + name: 'query_var', + datasource: { uid: '${ds_var}', type: 'prometheus' }, + type: 'query', + }, + ], + }, + }; + const dashboardModel = new DashboardModel(dashboard, undefined, { + getVariablesFromState: () => dashboard.templating!.list! as TypedVariableModel[], + }); + const exported = (await makeExportableV1(dashboardModel)) as DashboardJson; + + // @ts-ignore + const queryVarDatasource = exported.templating?.list[1].datasource; + expect(queryVarDatasource).toEqual({ uid: '${ds_var}', type: 'prometheus' }); + }); + describe('given dashboard with repeated panels', () => { let dash: any, exported: any; diff --git a/public/app/features/dashboard-scene/scene/export/exporters.ts b/public/app/features/dashboard-scene/scene/export/exporters.ts index 01a2ea9736b..96492361e8d 100644 --- a/public/app/features/dashboard-scene/scene/export/exporters.ts +++ b/public/app/features/dashboard-scene/scene/export/exporters.ts @@ -128,6 +128,12 @@ export async function makeExportableV1(dashboard: DashboardModel) { if (match) { varName = match[1] || match[2] || match[4]; datasourceVariable = variableLookup[varName]; + + // if datasource variable is already templated, skip it + if (datasourceVariableRefNameMap[varName]) { + return; + } + if (datasourceVariable && datasourceVariable.current) { datasource = datasourceVariable.current.value; } @@ -172,7 +178,7 @@ export async function makeExportableV1(dashboard: DashboardModel) { }; } - // if it panel or query is relying on a datasource variable + // if panel or query is relying on a datasource variable // skip templating datasource uid but save the reference so we can set datasource variable's current prop if (datasourceVariable && varName) { datasourceVariableRefNameMap[varName] = '${' + refName + '}'; diff --git a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx index 7ba318f2f4b..7e8570a3eec 100644 --- a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx +++ b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx @@ -1,6 +1,6 @@ import { createRef, CSSProperties, PointerEvent as ReactPointerEvent } from 'react'; -import { SceneLayout, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { SceneLayout, SceneObjectBase, SceneObjectState, VizPanel, SceneGridItemLike } from '@grafana/scenes'; import { isRepeatCloneOrChildOf } from '../../utils/clone'; import { getLayoutOrchestratorFor } from '../../utils/utils'; @@ -110,7 +110,12 @@ export class AutoGridLayout extends SceneObjectBase impleme public getDragHooks() { return { - onDragStart: this._onDragStart, + onDragStart: (evt: ReactPointerEvent, panel: VizPanel) => { + const gridItem = panel.parent; + if (gridItem instanceof AutoGridItem) { + this._onDragStart(evt, gridItem); + } + }, }; } @@ -127,7 +132,7 @@ export class AutoGridLayout extends SceneObjectBase impleme } // Start inside dragging - private _onDragStart(evt: ReactPointerEvent, panel: VizPanel) { + private _onDragStart(evt: ReactPointerEvent, gridItem: SceneGridItemLike) { if (!this._canDrag(evt)) { return; } @@ -135,11 +140,11 @@ export class AutoGridLayout extends SceneObjectBase impleme evt.preventDefault(); evt.stopPropagation(); - if (!(panel.parent instanceof AutoGridItem)) { + if (!(gridItem instanceof AutoGridItem)) { throw new Error('Dragging wrong item'); } - this._draggedGridItem = panel.parent; + this._draggedGridItem = gridItem; const { top, left, width, height } = this._draggedGridItem.getBoundingBox(); this._initialGridItemPosition = { pageX: evt.pageX, pageY: evt.pageY, top, left: left }; @@ -152,7 +157,7 @@ export class AutoGridLayout extends SceneObjectBase impleme document.body.addEventListener('pointerup', this._onDragEnd); document.body.classList.add('dashboard-draggable-transparent-selection'); - getLayoutOrchestratorFor(this)?.startDraggingSync(evt, panel); + getLayoutOrchestratorFor(this)?.startDraggingSync(evt, this._draggedGridItem); } // Stop inside dragging diff --git a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayoutManager.tsx index 737629562fc..74ce363a970 100644 --- a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayoutManager.tsx @@ -1,6 +1,13 @@ import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; -import { SceneComponentProps, SceneObject, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { + SceneComponentProps, + SceneObject, + SceneObjectBase, + SceneObjectState, + VizPanel, + SceneGridItemLike, +} from '@grafana/scenes'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { GRID_CELL_VMARGIN } from 'app/core/constants'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -325,6 +332,33 @@ export class AutoGridLayoutManager extends SceneObjectBase) { diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index 294dc4230f7..44a69b8fb23 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -125,9 +125,12 @@ export class DefaultGridLayoutManager private _activationHandler() { if (config.featureToggles.dashboardNewLayouts) { this._subs.add( - this.subscribeToEvent(SceneGridLayoutDragStartEvent, ({ payload: { evt, panel } }) => - getLayoutOrchestratorFor(this)?.startDraggingSync(evt, panel) - ) + this.subscribeToEvent(SceneGridLayoutDragStartEvent, ({ payload: { evt, panel } }) => { + const gridItem = panel.parent; + if (gridItem instanceof DashboardGridItem) { + getLayoutOrchestratorFor(this)?.startDraggingSync(evt, gridItem); + } + }) ); } @@ -515,6 +518,51 @@ export class DefaultGridLayoutManager }); } + public addGridItem(gridItem: SceneGridItemLike): void { + if (!(gridItem instanceof DashboardGridItem)) { + // If it's an AutoGridItem, convert it to DashboardGridItem + if (gridItem instanceof AutoGridItem) { + if (!(gridItem.state.body instanceof VizPanel)) { + throw new Error('AutoGridItem body is not a VizPanel'); + } + const panel = gridItem.state.body; + panel.clearParent(); + + const emptySpace = findSpaceForNewPanel(this.state.grid); + const newGridItem = new DashboardGridItem({ + x: emptySpace?.x ?? 0, + y: emptySpace?.y ?? 0, + width: emptySpace?.width ?? NEW_PANEL_WIDTH, + height: emptySpace?.height ?? NEW_PANEL_HEIGHT, + itemHeight: emptySpace?.height ?? NEW_PANEL_HEIGHT, + body: panel, + variableName: gridItem.state.variableName, + }); + + this.state.grid.setState({ children: [...this.state.grid.state.children, newGridItem] }); + return; + } + throw new Error('Grid item must be a DashboardGridItem or AutoGridItem'); + } + + // Move the whole grid item to another CustomGrid + // Clear parent before moving + gridItem.clearParent(); + + // Find empty space for the grid item, preserving its size + const emptySpace = findSpaceForNewPanel(this.state.grid); + if (emptySpace) { + // Update position to empty space, but keep original size + gridItem.setState({ + x: emptySpace.x, + y: emptySpace.y, + // Keep original width and height + }); + } + + this.state.grid.setState({ children: [...this.state.grid.state.children, gridItem] }); + } + public static createFromLayout(currentLayout: DashboardLayoutManager): DefaultGridLayoutManager { const panels = currentLayout.getVizPanels(); const isLazy = getIsLazy(getDashboardSceneFor(currentLayout).state.preload)!; diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx index c9f7b91bf28..49a76045502 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { t } from '@grafana/i18n'; +import { logWarning } from '@grafana/runtime'; import { sceneGraph, SceneObject, SceneObjectBase, SceneObjectState, VariableDependencyConfig, - VizPanel, + SceneGridItemLike, + SceneGridLayout, } from '@grafana/scenes'; import { RowsLayoutRowKind } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import appEvents from 'app/core/app_events'; @@ -20,11 +22,15 @@ import { ConditionalRenderingGroup } from '../../conditional-rendering/group/Con import { serializeRow } from '../../serialization/layoutSerializers/RowsLayoutSerializer'; import { getElements } from '../../serialization/layoutSerializers/utils'; import { getDashboardSceneFor } from '../../utils/utils'; +import { AutoGridItem } from '../layout-auto-grid/AutoGridItem'; +import { AutoGridLayout } from '../layout-auto-grid/AutoGridLayout'; import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager'; +import { DashboardGridItem } from '../layout-default/DashboardGridItem'; import { clearClipboard } from '../layouts-shared/paste'; import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView'; import { BulkActionElement } from '../types/BulkActionElement'; import { DashboardDropTarget } from '../types/DashboardDropTarget'; +import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement'; import { LayoutParent } from '../types/LayoutParent'; @@ -168,14 +174,35 @@ export class RowItem } } - public draggedPanelOutside(panel: VizPanel) { - this.getLayout().removePanel?.(panel); + public draggedGridItemOutside?(gridItem: SceneGridItemLike): void { + // Remove from source layout + if (gridItem instanceof DashboardGridItem || gridItem instanceof AutoGridItem) { + const layout = gridItem.parent; + if (gridItem instanceof DashboardGridItem && layout instanceof SceneGridLayout) { + const newChildren = layout.state.children.filter((child) => child !== gridItem); + layout.setState({ children: newChildren }); + } else if (gridItem instanceof AutoGridItem && layout instanceof AutoGridLayout) { + const newChildren = layout.state.children.filter((child) => child !== gridItem); + layout.setState({ children: newChildren }); + } else { + const warningMessage = 'Grid item has unexpected parent type'; + console.warn(warningMessage); + logWarning(warningMessage); + } + } this.setIsDropTarget(false); } - public draggedPanelInside(panel: VizPanel) { - panel.clearParent(); - this.getLayout().addPanel(panel); + public draggedGridItemInside(gridItem: SceneGridItemLike): void { + const layout = this.getLayout(); + + if (isDashboardLayoutGrid(layout)) { + layout.addGridItem(gridItem); + } else { + const warningMessage = 'Layout manager does not support addGridItem'; + console.warn(warningMessage); + logWarning(warningMessage); + } this.setIsDropTarget(false); } diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx index f49e2a37746..5db9caebbf6 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { t } from '@grafana/i18n'; +import { logWarning } from '@grafana/runtime'; import { SceneObjectState, SceneObjectBase, sceneGraph, VariableDependencyConfig, SceneObject, - VizPanel, + SceneGridItemLike, + SceneGridLayout, } from '@grafana/scenes'; import { TabsLayoutTabKind } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { LS_TAB_COPY_KEY } from 'app/core/constants'; @@ -20,11 +22,15 @@ import { ConditionalRenderingGroup } from '../../conditional-rendering/group/Con import { serializeTab } from '../../serialization/layoutSerializers/TabsLayoutSerializer'; import { getElements } from '../../serialization/layoutSerializers/utils'; import { getDashboardSceneFor } from '../../utils/utils'; +import { AutoGridItem } from '../layout-auto-grid/AutoGridItem'; +import { AutoGridLayout } from '../layout-auto-grid/AutoGridLayout'; import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager'; +import { DashboardGridItem } from '../layout-default/DashboardGridItem'; import { clearClipboard } from '../layouts-shared/paste'; import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView'; import { BulkActionElement } from '../types/BulkActionElement'; import { DashboardDropTarget } from '../types/DashboardDropTarget'; +import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement'; import { LayoutParent } from '../types/LayoutParent'; @@ -186,18 +192,38 @@ export class TabItem } } - public draggedPanelOutside(panel: VizPanel) { - this.getLayout().removePanel?.(panel); + public draggedGridItemOutside?(gridItem: SceneGridItemLike): void { + // Remove from source layout + if (gridItem instanceof DashboardGridItem || gridItem instanceof AutoGridItem) { + const layout = gridItem.parent; + if (gridItem instanceof DashboardGridItem && layout instanceof SceneGridLayout) { + const newChildren = layout.state.children.filter((child) => child !== gridItem); + layout.setState({ children: newChildren }); + } else if (gridItem instanceof AutoGridItem && layout instanceof AutoGridLayout) { + const newChildren = layout.state.children.filter((child) => child !== gridItem); + layout.setState({ children: newChildren }); + } else { + const warningMessage = 'Grid item has unexpected parent type'; + console.warn(warningMessage); + logWarning(warningMessage); + } + } this.setIsDropTarget(false); } - public draggedPanelInside(panel: VizPanel) { - panel.clearParent(); - this.getLayout().addPanel(panel); + public draggedGridItemInside(gridItem: SceneGridItemLike): void { + const layout = this.getLayout(); + + if (isDashboardLayoutGrid(layout)) { + layout.addGridItem(gridItem); + } else { + const warningMessage = 'Layout manager does not support addGridItem'; + console.warn(warningMessage); + logWarning(warningMessage); + } this.setIsDropTarget(false); const parentLayout = this.getParentLayout(); - if (parentLayout.state.currentTabSlug !== this.getSlug()) { parentLayout.setState({ currentTabSlug: this.getSlug() }); } diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx index 58df0c52c7e..1687e099574 100644 --- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/PublicDashboardBadge.tsx @@ -13,17 +13,26 @@ export const PublicDashboardBadge = ({ dashboard }: ToolbarActionProps) => { return null; } - return ; + return ( + + ); }; // Used in old architecture export const PublicDashboardBadgeLegacy = PublicDashboardBadgeInternal; -function PublicDashboardBadgeInternal({ uid }: { uid: string }) { - const { data: publicDashboard } = useGetPublicDashboardQuery(uid); +function PublicDashboardBadgeInternal({ uid, hasPublicDashboard }: { uid: string; hasPublicDashboard?: boolean }) { + const { data: publicDashboard } = useGetPublicDashboardQuery(uid, { + skip: hasPublicDashboard !== undefined && !hasPublicDashboard, + }); const styles = useStyles2(getStyles); - if (!publicDashboard) { + const showBadge = hasPublicDashboard !== undefined ? hasPublicDashboard : !!publicDashboard; + + if (!showBadge) { return null; } diff --git a/public/app/features/dashboard-scene/scene/types/DashboardDropTarget.ts b/public/app/features/dashboard-scene/scene/types/DashboardDropTarget.ts index de588c5efbd..8e28d5be9f0 100644 --- a/public/app/features/dashboard-scene/scene/types/DashboardDropTarget.ts +++ b/public/app/features/dashboard-scene/scene/types/DashboardDropTarget.ts @@ -1,10 +1,10 @@ -import { SceneObject, VizPanel } from '@grafana/scenes'; +import { SceneObject, SceneGridItemLike } from '@grafana/scenes'; export interface DashboardDropTarget extends SceneObject { isDashboardDropTarget: Readonly; setIsDropTarget?(isDropTarget: boolean): void; - draggedPanelOutside?(panel: VizPanel): void; - draggedPanelInside?(panel: VizPanel): void; + draggedGridItemOutside?(gridItem: SceneGridItemLike): void; + draggedGridItemInside?(gridItem: SceneGridItemLike): void; } export function isDashboardDropTarget(scene: SceneObject): scene is DashboardDropTarget { diff --git a/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts b/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts index 3f6bcb2bcc6..877a84893aa 100644 --- a/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts +++ b/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts @@ -1,3 +1,5 @@ +import { SceneGridItemLike } from '@grafana/scenes'; + import { DashboardLayoutManager } from './DashboardLayoutManager'; export interface DashboardLayoutGrid extends DashboardLayoutManager { @@ -5,8 +7,12 @@ export interface DashboardLayoutGrid extends DashboardLayoutManager { * Merge the layout with another layout */ mergeGrid(other: DashboardLayoutGrid): void; + /** + * Add a grid item to the layout + */ + addGridItem(gridItem: SceneGridItemLike): void; } export function isDashboardLayoutGrid(obj: DashboardLayoutManager): obj is DashboardLayoutGrid { - return 'mergeGrid' in obj; + return 'mergeGrid' in obj && 'addGridItem' in obj; } diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index f5fa4264fb8..9914559093a 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -11,6 +11,7 @@ import { Spec as DashboardV2Spec, defaultSpec as defaultDashboardV2Spec, defaultDataQueryKind, + defaultPanelQueryKind, defaultPanelSpec, GridLayoutKind, PanelKind, @@ -783,7 +784,7 @@ describe('DashboardSceneSerializer', () => { data: { kind: 'QueryGroup', spec: { - queries: [], + queries: [defaultPanelQueryKind()], queryOptions: {}, transformations: [], }, diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.ts b/public/app/features/dashboard-scene/serialization/angularMigration.ts index 606cf8545e4..81649cfadd3 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.ts @@ -5,7 +5,7 @@ import { autoMigrateAngular, PanelModel } from 'app/features/dashboard/state/Pan export function getAngularPanelMigrationHandler(oldModel: PanelModel) { return function handleAngularPanelMigrations(panel: PanelModelFromData, plugin: PanelPlugin) { - if (plugin.angularPanelCtrl) { + if ('angularPanelCtrl' in plugin && plugin.angularPanelCtrl) { panel.options = { angularOptions: oldModel.getOptionsToRemember() }; return; } diff --git a/public/app/features/dashboard-scene/serialization/annotations.ts b/public/app/features/dashboard-scene/serialization/annotations.ts index 63aa4002675..dbc4639fdad 100644 --- a/public/app/features/dashboard-scene/serialization/annotations.ts +++ b/public/app/features/dashboard-scene/serialization/annotations.ts @@ -1,11 +1,14 @@ import { AnnotationQuery } from '@grafana/data'; -import { AnnotationQueryKind, defaultDataQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2'; +import { + AnnotationQueryKind, + defaultAnnotationQuerySpec, + defaultDataQueryKind, +} from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { getRuntimePanelDataSource } from './layoutSerializers/utils'; export function transformV1ToV2AnnotationQuery( annotation: AnnotationQuery, - dsType: string, dsUID?: string, // Overrides are used to provide properties based on scene's annotations data layer object state @@ -36,10 +39,10 @@ export function transformV1ToV2AnnotationQuery( kind: 'AnnotationQuery', spec: { builtIn: Boolean(annotation.builtIn), - name: annotation.name, + name: annotation.name ?? defaultAnnotationQuerySpec().name, enable: Boolean(override?.enable) || Boolean(annotation.enable), hide: Boolean(override?.hide) || Boolean(annotation.hide), - iconColor: annotation.iconColor, + iconColor: annotation.iconColor ?? defaultAnnotationQuerySpec().iconColor, query: { kind: 'DataQuery', @@ -50,17 +53,18 @@ export function transformV1ToV2AnnotationQuery( }, }; - // Add placement if it exists - if (annotation.placement) { - result.spec.placement = annotation.placement; - } - - if (dsUID) { + if (dsUID && annotation.datasource && annotation.datasource.type) { + // Only add datasource reference if the annotation actually has a valid datasource in the original input result.spec.query.datasource = { name: dsUID, }; } + // Add placement if it exists + if (annotation.placement) { + result.spec.placement = annotation.placement; + } + // if legacy options is not an empty object, add it to the result if (Object.keys(legacyOptions).length > 0) { result.spec.legacyOptions = legacyOptions; diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts index 81e901c0a32..fb5f906d243 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts @@ -17,11 +17,28 @@ export function serializeRowsLayout(layoutManager: RowsLayoutManager): Dashboard export function serializeRow(row: RowItem): RowsLayoutRowKind { const layout = row.state.layout.serialize(); + + // Normalize Y coordinates to be relative within the row + // Panels in the scene have absolute Y coordinates, but in V2 schema they should be relative to the row + if (layout.kind === 'GridLayout' && layout.spec.items.length > 0) { + // Find the minimum Y coordinate among all items in this row + const minY = Math.min(...layout.spec.items.map((item) => item.spec.y)); + + // Subtract minY from each item's Y to make coordinates relative to the row start + layout.spec.items = layout.spec.items.map((item) => ({ + ...item, + spec: { + ...item.spec, + y: item.spec.y - minY, + }, + })); + } + const rowKind: RowsLayoutRowKind = { kind: 'RowsLayoutRow', spec: { title: row.state.title, - collapse: row.state.collapse, + collapse: row.state.collapse ?? false, layout: layout, fillScreen: row.state.fillScreen, hideHeader: row.state.hideHeader, diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index 3f8c2edf01e..566bb7c367a 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -20,6 +20,7 @@ import { QueryVariableKind, TabsLayoutTabKind, DataQueryKind, + defaultPanelQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; @@ -133,7 +134,12 @@ export function buildLibraryPanel(panel: LibraryPanelKind, id?: number): VizPane export function createPanelDataProvider(panelKind: PanelKind): SceneDataProvider | undefined { const panel = panelKind.spec; - const targets = panel.data?.spec.queries ?? []; + + const targets = + // Default to an array with an empty data query with a `refId` already assigned + Array.isArray(panel.data?.spec.queries) && panel.data?.spec.queries.length > 0 + ? panel.data?.spec.queries + : [defaultPanelQueryKind()]; // Skip setting query runner for panels without queries if (!targets?.length) { return undefined; @@ -207,7 +213,7 @@ export function getRuntimeVariableDataSource(variable: QueryVariableKind): DataS return getDataSourceForQuery(ds, variable.spec.query.group); } -export function getRuntimePanelDataSource(query: DataQueryKind): DataSourceRef { +export function getRuntimePanelDataSource(query: DataQueryKind): DataSourceRef | undefined { const ds: DataSourceRef = { uid: query.datasource?.name, type: query.group, @@ -274,8 +280,9 @@ export function getDataSourceForQuery(querySpecDS: DataSourceRef | undefined | n function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { return { refId: query.spec.refId, - datasource: getRuntimePanelDataSource(query.spec.query), hide: query.spec.hidden, + // If the query has no group, it means it's a default empty query, so we don't need to set a datasource + ...(!query.spec.query.group ? {} : { datasource: getRuntimePanelDataSource(query.spec.query) }), ...query.spec.query.spec, }; } diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index 64fe4c49293..49ef208a31d 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -27,6 +27,7 @@ import { defaultDataQueryKind, AdHocFilterWithLabels, SwitchVariableKind, + defaultIntervalVariableSpec, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { getDefaultDatasource } from 'app/features/dashboard/api/ResponseTransformers'; @@ -34,7 +35,7 @@ import { getIntervalsQueryFromNewIntervalModel } from '../utils/utils'; import { DSReferencesMapping } from './DashboardSceneSerializer'; import { getDataSourceForQuery } from './layoutSerializers/utils'; -import { getDataQueryKind, getDataQuerySpec, getElementDatasource } from './transformSceneToSaveModelSchemaV2'; +import { getDataQueryKind, getElementDatasource } from './transformSceneToSaveModelSchemaV2'; import { transformVariableRefreshToEnum, transformVariableHideToEnum, @@ -332,27 +333,30 @@ export function sceneVariablesSetToSchemaV2Variables( dataQuery = { kind: 'DataQuery', version: defaultDataQueryKind().version, - group: datasource?.type ?? getDataQueryKind(query), + group: datasource?.type || getDataQueryKind(query), ...(datasource?.uid && { datasource: { name: datasource.uid, }, }), - spec: getDataQuerySpec(query), + spec: query, }; } else { + // Only include LEGACY_STRING_VALUE_KEY if query is a non-empty string + const spec: Record = {}; + if (query) { + spec[LEGACY_STRING_VALUE_KEY] = query; + } dataQuery = { kind: 'DataQuery', version: defaultDataQueryKind().version, - group: datasource?.type ?? getDataQueryKind(query), + group: datasource?.type || getDataQueryKind(query), ...(datasource?.uid && { datasource: { name: datasource.uid, }, }), - spec: { - [LEGACY_STRING_VALUE_KEY]: query, - }, + spec, }; } const queryVariable: QueryVariableKind = { @@ -365,7 +369,7 @@ export function sceneVariablesSetToSchemaV2Variables( definition: variable.state.definition, sort: transformSortVariableToEnum(variable.state.sort), refresh: transformVariableRefreshToEnum(variable.state.refresh), - regex: variable.state.regex, + regex: variable.state.regex ?? '', allValue: variable.state.allValue, includeAll: variable.state.includeAll || false, multi: variable.state.isMulti || false, @@ -407,9 +411,9 @@ export function sceneVariablesSetToSchemaV2Variables( ...commonProperties, current: currentVariableOption, options: [], - regex: variable.state.regex, + regex: variable.state.regex ?? '', refresh: 'onDashboardLoad', - pluginId: variable.state.pluginId, + pluginId: variable.state.pluginId ?? getDefaultDatasource().type, multi: variable.state.isMulti || false, includeAll: variable.state.includeAll || false, allowCustomValue: variable.state.allowCustomValue ?? true, @@ -424,17 +428,16 @@ export function sceneVariablesSetToSchemaV2Variables( // Constant variable } else if (sceneUtils.isConstantVariable(variable)) { + const value = variable.state.value ? String(variable.state.value) : ''; const constantVariable: ConstantVariableKind = { kind: 'ConstantVariable', spec: { ...commonProperties, current: { - ...currentVariableOption, - // Constant variable doesn't use text state - text: String(variable.state.value), + text: value, + value: value, }, - // @ts-expect-error - query: variable.state.value, + query: value, }, }; variables.push(constantVariable); @@ -452,15 +455,15 @@ export function sceneVariablesSetToSchemaV2Variables( text: variable.state.value, }, query: intervals, - refresh: 'onTimeRangeChanged', + refresh: defaultIntervalVariableSpec().refresh, options: variable.state.intervals.map((interval) => ({ value: interval, text: interval, selected: interval === variable.state.value, })), - auto: variable.state.autoEnabled, - auto_min: variable.state.autoMinInterval, - auto_count: variable.state.autoStepCount, + auto: variable.state.autoEnabled ?? defaultIntervalVariableSpec().auto, + auto_min: variable.state.autoMinInterval ?? defaultIntervalVariableSpec().auto_min, + auto_count: variable.state.autoStepCount ?? defaultIntervalVariableSpec().auto_count, }, }; variables.push(intervalVariable); @@ -468,8 +471,8 @@ export function sceneVariablesSetToSchemaV2Variables( // Textbox variable } else if (sceneUtils.isTextBoxVariable(variable)) { const current = { - text: variable.state.value, - value: variable.state.value, + text: variable.state.value ?? '', + value: variable.state.value ?? '', }; const textBoxVariable: TextVariableKind = { @@ -477,7 +480,7 @@ export function sceneVariablesSetToSchemaV2Variables( spec: { ...commonProperties, current, - query: variable.state.value, + query: variable.state.value ?? '', }, }; diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index d536fdd1875..d0ec5616de5 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -148,6 +148,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo 0) { + // Extract intervals from options when query is missing (matches backend behavior) + intervals = variable.spec.options.map((opt) => String(opt.value || opt.text)).filter(Boolean); + } else { + // Fallback to default intervals only if both query and options are missing + intervals = getIntervalsFromQueryString(''); + } const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); return new IntervalVariable({ ...commonProperties, @@ -449,16 +460,23 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S function getDataQueryForVariable(variable: QueryVariableKind) { return LEGACY_STRING_VALUE_KEY in variable.spec.query.spec ? (variable.spec.query.spec[LEGACY_STRING_VALUE_KEY] ?? '') - : { - ...variable.spec.query.spec, - refId: variable.spec.query.spec.refId ?? 'A', - }; + : variable.spec.query.spec; } export function getCurrentValueForOldIntervalModel(variable: IntervalVariableKind, intervals: string[]): string { - const selectedInterval = Array.isArray(variable.spec.current.value) - ? variable.spec.current.value[0] - : variable.spec.current.value; + // Handle missing current object or value + const currentValue = variable.spec.current?.value; + const selectedInterval = Array.isArray(currentValue) ? currentValue[0] : currentValue; + + // If no intervals are available, return empty string (will use default from IntervalVariable) + if (intervals.length === 0) { + return ''; + } + + // If no selected interval, return the first valid interval + if (!selectedInterval) { + return intervals[0]; + } // If the interval is the old auto format, return the new auto interval from scenes. if (selectedInterval.startsWith('$__auto_interval_')) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 9f6c95a945a..25cb5d55e44 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -242,7 +242,9 @@ function createRowItemFromLegacyRow(row: PanelModel, panels: DashboardGridItem[] layout: new DefaultGridLayoutManager({ grid: new SceneGridLayout({ // If the row is collapsed it will have panels within the row model. - children: (row.panels?.map((p) => buildGridItemForPanel(p)) ?? []).concat(panels), + children: ( + row.panels?.map((p) => buildGridItemForPanel(p instanceof PanelModel ? p : new PanelModel(p))) ?? [] + ).concat(panels), }), }), repeatByVariable: row.repeat, diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts new file mode 100644 index 00000000000..9838f889a6d --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts @@ -0,0 +1,299 @@ +import { readdirSync, readFileSync } from 'fs'; +import path from 'path'; + +import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene'; +import { transformSaveModelToScene } from './transformSaveModelToScene'; +import { transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2'; + +// Mock the config to provide datasource information +jest.mock('@grafana/runtime', () => { + const mockConfig = { + ...jest.requireActual('@grafana/runtime').config, + defaultDatasource: 'default-ds-uid', + datasources: { + '-- Grafana --': { + type: 'grafana', + uid: '-- Grafana --', + name: 'Grafana', + meta: { id: 'grafana' }, + }, + 'existing-ref-uid': { + type: 'prometheus', + uid: 'existing-ref-uid', + name: 'Prometheus', + meta: { id: 'prometheus' }, + }, + 'influxdb-uid': { + type: 'influxdb', + uid: 'influxdb-uid', + name: 'InfluxDB', + meta: { id: 'influxdb' }, + }, + 'cloudwatch-uid': { + type: 'cloudwatch', + uid: 'cloudwatch-uid', + name: 'CloudWatch', + meta: { id: 'cloudwatch' }, + }, + 'elasticsearch-uid': { + type: 'elasticsearch', + uid: 'elasticsearch-uid', + name: 'Elasticsearch', + meta: { id: 'elasticsearch' }, + }, + 'loki-uid': { + type: 'loki', + uid: 'loki-uid', + name: 'Loki', + meta: { id: 'loki' }, + }, + 'default-ds-uid': { + type: 'prometheus', + uid: 'default-ds-uid', + name: 'Default Prometheus', + meta: { id: 'prometheus' }, + }, + 'existing-target-uid': { + type: 'elasticsearch', + uid: 'existing-target-uid', + name: 'Elasticsearch Target', + meta: { id: 'elasticsearch' }, + }, + 'non-default-test-ds-uid': { + type: 'loki', + uid: 'non-default-test-ds-uid', + name: 'Loki Test', + meta: { id: 'loki' }, + }, + '-- Mixed --': { + type: 'mixed', + uid: '-- Mixed --', + name: '-- Mixed --', + meta: { id: 'mixed' }, + }, + }, + featureToggles: { + dashboardNewLayouts: true, + kubernetesDashboards: true, + unifiedAlertingEnabled: true, + scopeFilters: false, + reloadDashboardsOnParamsChange: false, + timeComparison: false, + }, + dashboardPerformanceMetrics: [], + quickRanges: [], + panelSeriesLimit: 1000, + publicDashboardAccessToken: undefined, + }; + + return { + ...jest.requireActual('@grafana/runtime'), + config: mockConfig, + }; +}); + +/* + * V1 to V2 Dashboard Transformation Comparison Test + * + * This test compares frontend and backend transformations of dashboard data from v1 to v2 format. + * It uses the same test data as the backend conversion tests and verifies that the frontend + * transformation produces equivalent results to the backend transformation. + */ + +describe('V1 to V2 Dashboard Transformation Comparison', () => { + beforeEach(() => { + jest.clearAllMocks(); + + // Mock console methods to avoid test failures from expected warnings + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + const inputDir = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'apps', + 'dashboard', + 'pkg', + 'migration', + 'conversion', + 'testdata', + 'input' + ); + const outputDir = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'apps', + 'dashboard', + 'pkg', + 'migration', + 'conversion', + 'testdata', + 'output' + ); + const migratedInput = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'apps', + 'dashboard', + 'pkg', + 'migration', + 'testdata', + 'output', + 'latest_version' + ); + const migratedOutput = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'apps', + 'dashboard', + 'pkg', + 'migration', + 'conversion', + 'testdata', + 'migrated_dashboards_output' + ); + + const jsonInputs = readdirSync(inputDir); + const LATEST_API_VERSION = 'dashboard.grafana.app/v2beta1'; + + // Filter to only process v1beta1 input files + const v1beta1Inputs = jsonInputs.filter((inputFile) => inputFile.startsWith('v1beta1.')); + + v1beta1Inputs.forEach((inputFile) => { + it(`compare ${inputFile} from v1beta1 to v2beta1 backend and frontend conversions`, async () => { + const jsonInput = JSON.parse(readFileSync(path.join(inputDir, inputFile), 'utf8')); + + // Find the corresponding v2beta1 output file + const outputFileName = inputFile.replace('.json', `.${LATEST_API_VERSION.split('/')[1]}.json`); + const outputFilePath = path.join(outputDir, outputFileName); + + // Load the backend output + const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); + expect(backendOutput.apiVersion).toBe(LATEST_API_VERSION); + + // Load the backend output into a scene, then transform it back to a save model schema v2 + // This is to ensure that the backend output is the same as the frontend output being loaded by the scene + const sceneBackend = transformSaveModelSchemaV2ToScene({ + spec: backendOutput.spec, + metadata: backendOutput.metadata, + apiVersion: backendOutput.apiVersion, + access: {}, + kind: 'DashboardWithAccessInfo', + }); + const backendOutputAfterLoadedByScene = transformSceneToSaveModelSchemaV2(sceneBackend, false); + + // Transform using frontend path: v1beta1 -> Scene -> v2beta1 + // Extract the spec from v1beta1 format and use it as the dashboard data + // Remove snapshot field to prevent isSnapshot() from returning true + const dashboardSpec = { ...jsonInput.spec }; + delete dashboardSpec.snapshot; + + // Wrap in DashboardDTO structure that transformSaveModelToScene expects + const scene = transformSaveModelToScene({ + dashboard: dashboardSpec, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, + }); + + const frontendOutput = transformSceneToSaveModelSchemaV2(scene, false); + + // Verify both outputs have valid spec structures + expect(frontendOutput).toBeDefined(); + expect(backendOutputAfterLoadedByScene).toBeDefined(); + + // Compare only the spec structures - this is the core transformation + expect(backendOutputAfterLoadedByScene).toEqual(frontendOutput); + }); + }); + + // Test migrated dashboards (from migration pipeline output) + const migratedJsonInputs = readdirSync(migratedInput); + + migratedJsonInputs.forEach((inputFile) => { + it(`compare migrated ${inputFile} from v1beta1 to v2beta1 backend and frontend conversions`, async () => { + // Read the raw dashboard JSON from migration output (latest_version directory) + const jsonInput = JSON.parse(readFileSync(path.join(migratedInput, inputFile), 'utf8')); + + // Find the corresponding v2beta1 output file in migrated_dashboards_output + // The backend test prefixes these with "v1beta1-mig-" + const outputFileName = `v1beta1-mig-${inputFile.replace('.json', '')}.${LATEST_API_VERSION.split('/')[1]}.json`; + const outputFilePath = path.join(migratedOutput, outputFileName); + + // Load the backend output + const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); + expect(backendOutput.apiVersion).toBe(LATEST_API_VERSION); + + // Load the backend output into a scene, then transform it back to a save model schema v2 + // This is to ensure that the backend output is the same as the frontend output being loaded by the scene + const sceneBackend = transformSaveModelSchemaV2ToScene({ + spec: backendOutput.spec, + metadata: backendOutput.metadata, + apiVersion: backendOutput.apiVersion, + access: {}, + kind: 'DashboardWithAccessInfo', + }); + const backendOutputAfterLoadedByScene = transformSceneToSaveModelSchemaV2(sceneBackend, false); + + // Transform using frontend path: raw dashboard JSON -> Scene -> v2beta1 + // These files are raw dashboard JSON (already migrated to v42), not wrapped in v1beta1 API format + // Remove snapshot field to prevent isSnapshot() from returning true + const dashboardSpec = { ...jsonInput }; + delete dashboardSpec.snapshot; + + // Wrap in DashboardDTO structure that transformSaveModelToScene expects + const scene = transformSaveModelToScene({ + dashboard: dashboardSpec, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, + }); + + const frontendOutput = transformSceneToSaveModelSchemaV2(scene, false); + + // Verify both outputs have valid spec structures + expect(frontendOutput).toBeDefined(); + expect(backendOutputAfterLoadedByScene).toBeDefined(); + + // Compare only the spec structures - this is the core transformation + expect(backendOutputAfterLoadedByScene).toEqual(frontendOutput); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 190a590f1ad..dbf713564a6 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -31,6 +31,7 @@ import { AutoGridLayoutSpec, RowsLayoutSpec, TabsLayoutSpec, + defaultDataQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { DashboardEditPane } from '../edit-pane/DashboardEditPane'; @@ -113,12 +114,12 @@ jest.mock('@grafana/runtime', () => ({ Prometheus: { name: 'Prometheus', meta: { id: 'prometheus' }, - type: 'datasource', + type: 'prometheus', }, '-- Grafana --': { name: 'Grafana', meta: { id: 'grafana' }, - type: 'datasource', + type: 'grafana', }, loki: { name: 'Loki', @@ -130,7 +131,7 @@ jest.mock('@grafana/runtime', () => ({ module: 'app/plugins/datasource/loki/module', baseUrl: '/plugins/loki', }, - type: 'datasource', + type: 'loki', }, }, }, @@ -847,7 +848,7 @@ describe('getVizPanelQueries', () => { expect(result.length).toBe(2); expect(result[0].spec.query.kind).toBe('DataQuery'); expect(result[0].spec.query.datasource).toBeUndefined(); // ignore datasource if it wasn't provided - expect(result[0].spec.query.group).toBe('default'); + expect(result[0].spec.query.group).toBe(defaultDataQueryKind().group); // this is a default query that contains only refId and therefore group should be the default group expect(result[0].spec.query.version).toBe('v0'); expect(result[1].spec.query.kind).toBe('DataQuery'); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 85cd9e8ee7b..146cacb8f79 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -45,6 +45,9 @@ import { defaultFieldConfig, defaultDataQueryKind, SwitchVariableKind, + defaultTimeSettingsSpec, + defaultDashboardLinkType, + defaultDashboardLink, } from '../../../../../packages/grafana-schema/src/schema/dashboard/v2'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; @@ -73,6 +76,8 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps const dsReferencesMapping: DSReferencesMapping = scene.serializer.getDSReferencesMapping(); + const timeSettingsDefaults = defaultTimeSettingsSpec(); + const dashboardSchemaV2: DeepPartial = { //dashboard settings title: sceneDash.title, @@ -81,18 +86,29 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps liveNow: getLiveNow(sceneDash), preload: sceneDash.preload ?? defaultDashboardV2Spec().preload, editable: sceneDash.editable ?? defaultDashboardV2Spec().editable, - links: sceneDash.links ?? defaultDashboardV2Spec().links, + links: (sceneDash.links || []).map((link) => ({ + title: link.title ?? defaultDashboardLink().title, + url: link.url ?? defaultDashboardLink().url, + type: link.type ?? defaultDashboardLinkType(), + icon: link.icon ?? defaultDashboardLink().icon, + tooltip: link.tooltip ?? defaultDashboardLink().tooltip, + tags: link.tags ?? defaultDashboardLink().tags, + asDropdown: link.asDropdown ?? defaultDashboardLink().asDropdown, + keepTime: link.keepTime ?? defaultDashboardLink().keepTime, + includeVars: link.includeVars ?? defaultDashboardLink().includeVars, + targetBlank: link.targetBlank ?? defaultDashboardLink().targetBlank, + })), tags: sceneDash.tags ?? defaultDashboardV2Spec().tags, // EOF dashboard settings // time settings timeSettings: { - timezone: timeRange.timeZone, + timezone: timeRange.timeZone || timeSettingsDefaults.timezone, from: timeRange.from, to: timeRange.to, - autoRefresh: refreshPicker?.state.refresh || '', - autoRefreshIntervals: refreshPicker?.state.intervals, - hideTimepicker: controlsState?.hideTimeControls ?? false, + autoRefresh: refreshPicker?.state.refresh || timeSettingsDefaults.autoRefresh, + autoRefreshIntervals: refreshPicker?.state.intervals || timeSettingsDefaults.autoRefreshIntervals, + hideTimepicker: controlsState?.hideTimeControls || timeSettingsDefaults.hideTimepicker, weekStart: timeRange.weekStart, fiscalYearStartMonth: timeRange.fiscalYearStartMonth, nowDelay: timeRange.UNSAFE_nowDelay, @@ -179,8 +195,8 @@ export function vizPanelToSchemaV2( const defaults = handleFieldConfigDefaultsConversion(vizPanel); const vizFieldConfig: FieldConfigSource = { - ...vizPanel.state.fieldConfig, defaults, + overrides: vizPanel.state.fieldConfig?.overrides ?? [], }; const elementSpec: PanelKind = { @@ -249,6 +265,10 @@ function handleFieldConfigDefaultsConversion(vizPanel: VizPanel) { if (Array.isArray(value)) { return value.length > 0; } + // Filter out empty objects (like custom: {}) + if (typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length === 0) { + return false; + } return value !== undefined; }) ); @@ -271,37 +291,53 @@ export function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSR if (vizPanelQueries) { vizPanelQueries.forEach((query) => { - const queryDatasource = getElementDatasource(vizPanel, query, 'panel', queryRunner, dsReferencesMapping); + // Check if this is a default empty query (refId "A", empty spec) + // Default queries are injected during deserialization when panels have no queries + const querySpec = omit(query, 'datasource', 'refId', 'hide'); + const isDefaultEmptyQuery = + query.refId === 'A' && !query.datasource && vizPanelQueries.length === 1 && Object.keys(querySpec).length === 0; + + // For default empty queries, don't use getElementDatasource as it will assign a datasource + // Instead, leave datasource undefined so it gets deleted below + const queryDatasource = isDefaultEmptyQuery + ? undefined + : getElementDatasource(vizPanel, query, 'panel', queryRunner, dsReferencesMapping); const dataQuery: DataQueryKind = { kind: 'DataQuery', version: defaultDataQueryKind().version, - group: getDataQueryKind(query, queryRunner), + group: queryDatasource ? getDataQueryKind(query, queryRunner) : defaultDataQueryKind().group, datasource: { name: queryDatasource?.uid, }, - spec: omit(query, 'datasource', 'refId', 'hide'), + spec: querySpec, }; if (!dataQuery.datasource?.name) { delete dataQuery.datasource; } - const querySpec: PanelQuerySpec = { + const querySpecObj: PanelQuerySpec = { query: dataQuery, refId: query.refId, hidden: Boolean(query.hide), }; queries.push({ kind: 'PanelQuery', - spec: querySpec, + spec: querySpecObj, }); }); } return queries; } -export function getDataQueryKind(query: SceneDataQuery | string, queryRunner?: SceneQueryRunner): string { +export function getDataQueryKind(query: SceneDataQuery | string | undefined, queryRunner?: SceneQueryRunner): string { + // Handle undefined query - return default data source type + if (query === undefined || query === null) { + const defaultDS = getDefaultDataSourceRef(); + return defaultDS?.type || ''; + } + // Query is a string - get default data source type if (typeof query === 'string') { const defaultDS = getDefaultDataSourceRef(); @@ -313,8 +349,14 @@ export function getDataQueryKind(query: SceneDataQuery | string, queryRunner?: S return query.datasource.type; } - // Get type from query runner's datasource + // If query has a datasource UID (even without type), check if it matches the queryRunner's datasource + // Only use queryRunner's type if the UIDs match or if query has no datasource at all if (queryRunner?.state.datasource?.type) { + // If query has a datasource UID that differs from queryRunner's, fall back to default + if (query.datasource?.uid && query.datasource.uid !== queryRunner.state.datasource.uid) { + const defaultDS = getDefaultDataSourceRef(); + return defaultDS?.type || ''; + } return queryRunner.state.datasource.type; } @@ -323,10 +365,6 @@ export function getDataQueryKind(query: SceneDataQuery | string, queryRunner?: S return defaultDS?.type || ''; } -export function getDataQuerySpec(query: SceneDataQuery): DataQueryKind['spec'] { - return query; -} - function getVizPanelTransformations(vizPanel: VizPanel): TransformationKind[] { let transformations: TransformationKind[] = []; const dataProvider = vizPanel.state.$data; @@ -438,7 +476,7 @@ function getAnnotations(state: DashboardSceneState, dsReferencesMapping?: DSRefe let layerDs = layer.state.query.datasource; - if (!layerDs) { + if (!layerDs || !layerDs.type) { // This can happen only if we are transforming a scene that was created // from a v1 spec. In v1 annotation layer can contain no datasource ref, which is guaranteed // for layers created for v2 schema. See transform transformSaveModelSchemaV2ToScene.ts. @@ -497,7 +535,12 @@ export function getDefaultDataSourceRef(): DataSourceRef { const dsList = config.datasources; const ds = dsList[defaultDatasource]; - return { type: ds.meta.id, uid: ds.name }; // in the datasource list from bootData "id" is the type + // If we can't find the default datasource, fall back to grafana + if (!ds) { + return { type: 'grafana', uid: '-- Grafana --' }; + } + + return { type: ds.type, uid: ds.uid }; } export function trimDashboardForSnapshot(title: string, time: TimeRange, dash: DashboardV2Spec, panel?: VizPanel) { diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx index 765710f37aa..a3b0be31e80 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx @@ -68,6 +68,7 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDelete const onHideChange = (hide: VariableHide) => variable.setState({ hide }); const isHasVariableOptions = hasVariableOptions(variable); + const hasMultiProps = 'valuesFormat' in variable.state && variable.state.valuesFormat === 'json'; const onDeleteVariable = (hideModal: () => void) => () => { reportInteraction('Delete variable'); @@ -123,7 +124,9 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDelete {EditorToRender && } - {isHasVariableOptions && } + {isHasVariableOptions && ( + + )}
diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx index 29183c79531..5434068f21e 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx @@ -4,7 +4,6 @@ import { DataSourceInstanceSettings, MetricFindValue, readCSV } from '@grafana/d import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { EditorField } from '@grafana/plugin-ui'; -import { config } from '@grafana/runtime'; import { DataSourceRef } from '@grafana/schema'; import { Alert, CodeEditor, Field, Switch, Box } from '@grafana/ui'; import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; @@ -67,7 +66,7 @@ export function AdHocVariableForm({ onChange={onDataSourceChange} width={30} variables={true} - dashboard={config.featureToggles.dashboardDsAdHocFiltering} + dashboard={true} noDefault /> diff --git a/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx b/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx index 7faa4356c6c..7527886f524 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx @@ -4,20 +4,20 @@ import { MouseEvent, useCallback, useEffect, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; -import { MultiValueVariable, VariableValueOption } from '@grafana/scenes'; +import { VariableValueOption } from '@grafana/scenes'; import { Button, InlineFieldRow, InlineLabel, InteractiveTable, Text, useStyles2 } from '@grafana/ui'; export interface Props { - variable: MultiValueVariable; + options: VariableValueOption[]; + hasMultiProps?: boolean; } -export const VariableValuesPreview = ({ variable }: Props) => { - const options = variable.getOptionsForSelect(false); +export const VariableValuesPreview = ({ options, hasMultiProps }: Props) => { if (!options.length) { return null; } - if ('valuesFormat' in variable.state && variable.state.valuesFormat === 'json') { + if (hasMultiProps) { return ; } @@ -35,6 +35,7 @@ function VariableValuesWithPropsPreview({ options }: { options: VariableValueOpt Preview of values + {/* TODO: pageSize=20 */} String(r.value)} pageSize={2} />
); diff --git a/public/app/features/dashboard-scene/utils/tracking.ts b/public/app/features/dashboard-scene/utils/tracking.ts index 1b625612274..0be4cdd05ed 100644 --- a/public/app/features/dashboard-scene/utils/tracking.ts +++ b/public/app/features/dashboard-scene/utils/tracking.ts @@ -1,5 +1,6 @@ import { store } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { extractDatasourceTypesFromUrl } from 'app/features/dashboard/dashgrid/DashboardLibrary/utils/communityDashboardHelpers'; import { DashboardScene } from '../scene/DashboardScene'; import { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement'; @@ -55,16 +56,18 @@ export function trackDashboardSceneCreatedOrSaved( ) { // url values for dashboard library experiment const urlParams = new URLSearchParams(window.location.search); - const pluginId = urlParams.get('pluginId') || undefined; const sourceEntryPoint = urlParams.get('sourceEntryPoint') || undefined; - const libraryItemId = urlParams.get('libraryItemId') || undefined; + // For community dashboards, use gnetId as libraryItemId if libraryItemId is not present + const libraryItemId = urlParams.get('libraryItemId') || urlParams.get('gnetId') || undefined; const creationOrigin = urlParams.get('creationOrigin') || undefined; + // Extract datasourceTypes from URL params (supports both community and provisioned dashboards) + const datasourceTypes = extractDatasourceTypesFromUrl(); const dynamicDashboardsTrackingInformation = dashboard.getDynamicDashboardsTrackingInformation(); const dashboardLibraryProperties = config.featureToggles.dashboardLibrary ? { - datasourceTypes: [pluginId], + datasourceTypes, sourceEntryPoint, libraryItemId, creationOrigin, diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 419ce760b21..36ccabdca23 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -149,7 +149,11 @@ export function getMultiVariableValues(variable: MultiValueVariable | CustomVari } // used to transform old interval model to new interval model from scenes -export function getIntervalsFromQueryString(query: string): string[] { +export function getIntervalsFromQueryString(query: string | undefined): string[] { + if (!query) { + return initialIntervalVariableModelState.query?.split(',') ?? []; + } + // separate intervals by quotes either single or double const matchIntervals = query.match(/(["'])(.*?)\1|\w+/g); @@ -184,7 +188,19 @@ export function getIntervalsQueryFromNewIntervalModel(intervals: string[]): stri } export function getCurrentValueForOldIntervalModel(variable: IntervalVariableModel, intervals: string[]): string { - const selectedInterval = Array.isArray(variable.current.value) ? variable.current.value[0] : variable.current.value; + // Handle missing current object or value + const currentValue = variable.current?.value; + const selectedInterval = Array.isArray(currentValue) ? currentValue[0] : currentValue; + + // If no intervals are available, return empty string (will use default from IntervalVariable) + if (intervals.length === 0) { + return ''; + } + + // If no selected interval, return the first valid interval + if (!selectedInterval) { + return intervals[0]; + } // If the interval is the old auto format, return the new auto interval from scenes. if (selectedInterval.startsWith('$__auto_interval_') || selectedInterval === '$__auto') { diff --git a/public/app/features/dashboard-scene/utils/variables.ts b/public/app/features/dashboard-scene/utils/variables.ts index 3051fd69a93..f92cfe946fe 100644 --- a/public/app/features/dashboard-scene/utils/variables.ts +++ b/public/app/features/dashboard-scene/utils/variables.ts @@ -92,7 +92,17 @@ export function createSnapshotVariable(variable: TypedVariableModel): SceneVaria let snapshotVariable: SnapshotVariable; let current: { value: string | string[]; text: string | string[] }; if (variable.type === 'interval') { - const intervals = getIntervalsFromQueryString(variable.query); + // If query is missing, extract intervals from options instead of using defaults + let intervals: string[]; + if (variable.query) { + intervals = getIntervalsFromQueryString(variable.query); + } else if (variable.options && variable.options.length > 0) { + // Extract intervals from options when query is missing + intervals = variable.options.map((opt) => String(opt.value || opt.text)).filter(Boolean); + } else { + // Fallback to default intervals only if both query and options are missing + intervals = getIntervalsFromQueryString(''); + } const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); snapshotVariable = new SnapshotVariable({ name: variable.name, @@ -164,8 +174,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode ...commonProperties, value: variable.current?.value ?? '', text: variable.current?.text ?? '', - - query: variable.query, + query: variable.query || '', isMulti: variable.multi, allValue: variable.allValue || undefined, includeAll: variable.includeAll, @@ -181,7 +190,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode value: variable.current?.value ?? '', text: variable.current?.text ?? '', - query: variable.query, + query: variable.query ?? {}, datasource: variable.datasource, sort: variable.sort, refresh: variable.refresh, @@ -219,7 +228,17 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode }); // Interval variable } else if (variable.type === 'interval') { - const intervals = getIntervalsFromQueryString(variable.query); + // If query is missing, extract intervals from options instead of using defaults + let intervals: string[]; + if (variable.query) { + intervals = getIntervalsFromQueryString(variable.query); + } else if (variable.options && variable.options.length > 0) { + // Extract intervals from options when query is missing (matches backend behavior) + intervals = variable.options.map((opt) => String(opt.value || opt.text)).filter(Boolean); + } else { + // Fallback to default intervals only if both query and options are missing + intervals = getIntervalsFromQueryString(''); + } const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); return new IntervalVariable({ ...commonProperties, diff --git a/public/app/features/dashboard/api/ResponseTransformers.test.ts b/public/app/features/dashboard/api/ResponseTransformers.test.ts index 835f2a29866..5ce3e2ffadd 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.test.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.test.ts @@ -51,6 +51,7 @@ jest.mock('@grafana/runtime', () => ({ }, isDefault: true, apiVersion: 'v2', + type: 'prometheus', }, '-- Grafana --': { uid: 'grafana', @@ -62,6 +63,7 @@ jest.mock('@grafana/runtime', () => ({ type: 'datasource', }, isDefault: false, + type: 'datasource', }, }, @@ -74,7 +76,7 @@ describe('ResponseTransformers', () => { it('should return prometheus as default', () => { expect(getDefaultDatasource()).toEqual({ apiVersion: 'v2', - uid: 'PromTest', + uid: 'xyz-abc', type: 'prometheus', }); }); @@ -83,7 +85,7 @@ describe('ResponseTransformers', () => { describe('getDefaultDataSourceRef', () => { it('should return prometheus as default', () => { expect(getDefaultDataSourceRef()).toEqual({ - uid: 'PromTest', + uid: 'xyz-abc', type: 'prometheus', }); }); @@ -320,7 +322,10 @@ describe('ResponseTransformers', () => { targets: [ { refId: 'A', - datasource: 'datasource1', + datasource: { + uid: 'datasource1', + type: 'prometheus', + }, expr: 'test-query', hide: false, }, @@ -443,10 +448,11 @@ describe('ResponseTransformers', () => { description: '', id: 1, links: [], + transparent: false, vizConfig: { kind: 'VizConfig', group: 'timeseries', - version: undefined, + version: '', spec: { fieldConfig: { defaults: {}, @@ -478,15 +484,7 @@ describe('ResponseTransformers', () => { }, }, ], - queryOptions: { - cacheTimeout: undefined, - hideTimeOverride: undefined, - interval: undefined, - maxDataPoints: undefined, - queryCachingTTL: undefined, - timeFrom: undefined, - timeShift: undefined, - }, + queryOptions: {}, transformations: [], }, }, @@ -582,7 +580,10 @@ describe('ResponseTransformers', () => { targets: [ { refId: 'A', - datasource: 'datasource1', + datasource: { + type: 'prometheus', + uid: 'datasource1', + }, expr: 'test-query', hide: false, }, @@ -627,7 +628,10 @@ describe('ResponseTransformers', () => { targets: [ { refId: 'A', - datasource: 'datasource1', + datasource: { + type: 'prometheus', + uid: 'datasource1', + }, expr: 'test-query', hide: false, }, @@ -656,7 +660,10 @@ describe('ResponseTransformers', () => { targets: [ { refId: 'A', - datasource: 'datasource1', + datasource: { + type: 'prometheus', + uid: 'datasource1', + }, expr: 'test-query', hide: false, }, @@ -968,9 +975,12 @@ describe('ResponseTransformers', () => { const result = getPanelQueries(targets, panelDs); expect(result).toHaveLength(targets.length); + // @ts-expect-error expect(result[0].spec.refId).toBe('A'); + // @ts-expect-error expect(result[1].spec.refId).toBe('B'); + // @ts-expect-error result.forEach((query) => { expect(query.kind).toBe('PanelQuery'); expect(query.spec.query.group).toEqual('theoretical-ds'); @@ -996,9 +1006,12 @@ describe('ResponseTransformers', () => { const result = getPanelQueries(targets, panelDs); expect(result).toHaveLength(targets.length); + // @ts-expect-error expect(result[0].spec.refId).toBe('A'); + // @ts-expect-error expect(result[1].spec.refId).toBe('B'); + // @ts-expect-error result.forEach((query) => { expect(query.kind).toBe('PanelQuery'); expect(query.spec.query.group).toEqual('theoretical-ds'); diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index 319a21ca06f..ca8a48de32d 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -1,4 +1,4 @@ -import { TypedVariableModel } from '@grafana/data'; +import { MetricFindValue, TypedVariableModel } from '@grafana/data'; import { config } from '@grafana/runtime'; import { AnnotationQuery, @@ -20,7 +20,6 @@ import { DataLink, DatasourceVariableKind, defaultSpec as defaultDashboardV2Spec, - defaultFieldConfigSource, defaultTimeSettingsSpec, PanelQueryKind, QueryVariableKind, @@ -41,6 +40,10 @@ import { defaultDataQueryKind, RowsLayoutRowKind, GridLayoutKind, + defaultDashboardLinkType, + defaultDashboardLink, + defaultFieldConfigSource, + defaultPanelQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { DashboardLink, DataTransformerConfig } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen'; import { isWeekStart, WeekStart } from '@grafana/ui'; @@ -182,9 +185,11 @@ export function ensureV2Response( tags: dashboard.tags ?? [], cursorSync: transformCursorSynctoEnum(dashboard.graphTooltip), preload: dashboard.preload || dashboardDefaults.preload, - liveNow: dashboard.liveNow, - editable: dashboard.editable, - revision: dashboard.revision, + // transformSceneToSaveModelSchemaV2.ts sets liveNow and editable to default values if they are not set + // so we are matching that behavior here so conversion pipeline tests like ResponseTransformersToBackend.test.ts pass + liveNow: dashboard.liveNow ?? Boolean(dashboardDefaults.liveNow), + editable: dashboard.editable ?? dashboardDefaults.editable, + ...(dashboard.revision !== undefined && { revision: dashboard.revision }), timeSettings: { from: dashboard.time?.from || timeSettingsDefaults.from, to: dashboard.time?.to || timeSettingsDefaults.to, @@ -193,11 +198,24 @@ export function ensureV2Response( autoRefreshIntervals: dashboard.timepicker?.refresh_intervals || timeSettingsDefaults.autoRefreshIntervals, fiscalYearStartMonth: dashboard.fiscalYearStartMonth || timeSettingsDefaults.fiscalYearStartMonth, hideTimepicker: dashboard.timepicker?.hidden || timeSettingsDefaults.hideTimepicker, - quickRanges: dashboard.timepicker?.quick_ranges, - weekStart: getWeekStart(dashboard.weekStart, timeSettingsDefaults.weekStart), - nowDelay: dashboard.timepicker?.nowDelay || timeSettingsDefaults.nowDelay, + ...(dashboard.timepicker?.quick_ranges !== undefined && { quickRanges: dashboard.timepicker.quick_ranges }), + ...(dashboard.weekStart !== undefined && { + weekStart: getWeekStart(dashboard.weekStart, timeSettingsDefaults.weekStart), + }), + ...(dashboard.timepicker?.nowDelay !== undefined && { nowDelay: dashboard.timepicker.nowDelay }), }, - links: dashboard.links || [], + links: (dashboard.links || []).map((link) => ({ + title: link.title ?? defaultDashboardLink().title, + url: link.url ?? defaultDashboardLink().url, + type: link.type ?? defaultDashboardLinkType(), + icon: link.icon ?? defaultDashboardLink().icon, + tooltip: link.tooltip ?? defaultDashboardLink().tooltip, + tags: link.tags ?? defaultDashboardLink().tags, + asDropdown: link.asDropdown ?? defaultDashboardLink().asDropdown, + keepTime: link.keepTime ?? defaultDashboardLink().keepTime, + includeVars: link.includeVars ?? defaultDashboardLink().includeVars, + targetBlank: link.targetBlank ?? defaultDashboardLink().targetBlank, + })), annotations, variables, elements, @@ -232,6 +250,7 @@ export function ensureV1Response( uid: dashboard.metadata.name, k8s: dashboard.metadata, version: dashboard.metadata.generation, + publicDashboardEnabled: dashboard.access.isPublic, }, dashboard: spec, }; @@ -253,6 +272,7 @@ export function ensureV1Response( canShare: dashboard.access.canShare, canStar: dashboard.access.canStar, annotationsPermissions: dashboard.access.annotationsPermissions, + publicDashboardEnabled: dashboard.access.isPublic, }, dashboard: transformDashboardV2SpecToV1(spec, dashboard.metadata), }; @@ -387,7 +407,7 @@ function buildRowKind(p: RowPanel, elements: GridLayoutItemKind[]): RowsLayoutRo spec: { collapse: p.collapsed, title: p.title ?? '', - repeat: p.repeat ? { value: p.repeat, mode: 'variable' } : undefined, + ...(p.repeat ? { repeat: { value: p.repeat, mode: 'variable' } } : {}), layout: { kind: 'GridLayout', spec: { @@ -406,9 +426,16 @@ function buildGridItemKind(p: Panel, elementName: string, yOverride?: number): G y: yOverride ?? p.gridPos!.y, width: p.gridPos!.w, height: p.gridPos!.h, - repeat: p.repeat - ? { value: p.repeat, mode: 'variable', direction: p.repeatDirection, maxPerRow: p.maxPerRow } - : undefined, + ...(p.repeat + ? { + repeat: { + value: p.repeat, + mode: 'variable', + ...(p.repeatDirection !== undefined && { direction: p.repeatDirection }), + ...(p.maxPerRow !== undefined && { maxPerRow: p.maxPerRow }), + }, + } + : {}), element: { kind: 'ElementReference', name: elementName!, @@ -452,24 +479,26 @@ function getDefaultDatasourceType() { } export function getDefaultDatasource(): DataSourceRef { - const configDefaultDS = getDefaultDataSourceRef() ?? { type: 'grafana', uid: '-- Grafana --' }; + const defaultDataSourceRef = getDefaultDataSourceRef() ?? { type: 'grafana', uid: '-- Grafana --' }; - if (configDefaultDS.uid && !configDefaultDS.apiVersion) { + if (defaultDataSourceRef.uid && !defaultDataSourceRef.apiVersion) { // get api version from config - const dsInstance = config.datasources[configDefaultDS.uid]; - configDefaultDS.apiVersion = dsInstance.apiVersion ?? undefined; + const defaultDatasource = config.defaultDatasource; + const dsInstance = config.datasources[defaultDatasource]; + defaultDataSourceRef.apiVersion = dsInstance.apiVersion ?? undefined; } return { - apiVersion: configDefaultDS.apiVersion, - type: configDefaultDS.type, - uid: configDefaultDS.uid, + apiVersion: defaultDataSourceRef.apiVersion, + type: defaultDataSourceRef.type, + uid: defaultDataSourceRef.uid, }; } -export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourceRef): PanelQueryKind[] { +export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourceRef): PanelQueryKind[] | undefined { return targets.map((t) => { const { refId, hide, datasource, ...query } = t; + const ds = t.datasource || panelDatasource; const q: PanelQueryKind = { kind: 'PanelQuery', spec: { @@ -478,10 +507,12 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc query: { kind: 'DataQuery', version: defaultDataQueryKind().version, - group: t.datasource?.type || panelDatasource.type!, - datasource: { - name: t.datasource?.uid || panelDatasource.uid!, - }, + group: ds.type ?? '', + ...(ds.uid && { + datasource: { + name: ds.uid, + }, + }), spec: { ...query, }, @@ -493,44 +524,65 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc } export function buildPanelKind(p: Panel): PanelKind { - const queries = getPanelQueries((p.targets as unknown as DataQuery[]) || [], p.datasource || getDefaultDatasource()); + const queries = getPanelQueries((p.targets as unknown as DataQuery[]) || [], p.datasource ?? { type: '', uid: '' }); const transformations = getPanelTransformations(p.transformations || []); + const fieldConfig = p.fieldConfig || defaultFieldConfigSource(); + + // match backend conversion behavior + if (fieldConfig.defaults.mappings && fieldConfig.defaults.mappings.length === 0) { + delete fieldConfig.defaults.mappings; + } + // match backend conversion behavior + if (fieldConfig.defaults.custom && Object.keys(fieldConfig.defaults.custom).length === 0) { + delete fieldConfig.defaults.custom; + } + + // match backend conversion behavior + if ( + fieldConfig.defaults.thresholds?.steps && + fieldConfig.defaults.thresholds.steps.length > 0 && + !fieldConfig.defaults.thresholds.steps[0]?.value + ) { + fieldConfig.defaults.thresholds.steps[0]!.value = null; + } + const panelKind: PanelKind = { kind: 'Panel', spec: { title: p.title || '', description: p.description || '', + ...(p.transparent !== undefined && { transparent: p.transparent }), vizConfig: { kind: 'VizConfig', group: p.type, - version: p.pluginVersion!, + version: p.pluginVersion ?? '', spec: { - fieldConfig: (p.fieldConfig as any) || defaultFieldConfigSource(), - options: p.options as any, + fieldConfig: p.fieldConfig || defaultFieldConfigSource(), + options: p.options ?? {}, }, }, links: p.links?.map((l) => ({ title: l.title, url: l.url || '', - targetBlank: l.targetBlank, + ...(l.targetBlank !== undefined && { targetBlank: l.targetBlank }), })) || [], id: p.id!, data: { kind: 'QueryGroup', spec: { - queries, + queries: queries || [defaultPanelQueryKind()], transformations, queryOptions: { - cacheTimeout: p.cacheTimeout, - maxDataPoints: p.maxDataPoints, - interval: p.interval, - hideTimeOverride: p.hideTimeOverride, - queryCachingTTL: p.queryCachingTTL, - timeFrom: p.timeFrom, - timeShift: p.timeShift, + ...(p.cacheTimeout !== undefined && { cacheTimeout: p.cacheTimeout }), + ...(p.maxDataPoints !== undefined && { maxDataPoints: p.maxDataPoints }), + ...(p.interval !== undefined && { interval: p.interval }), + ...(p.hideTimeOverride !== undefined && { hideTimeOverride: p.hideTimeOverride }), + ...(p.queryCachingTTL !== undefined && { queryCachingTTL: p.queryCachingTTL }), + ...(p.timeFrom !== undefined && { timeFrom: p.timeFrom }), + ...(p.timeShift !== undefined && { timeShift: p.timeShift }), }, }, }, @@ -545,7 +597,7 @@ function getPanelTransformations(transformations: DataTransformerConfig[]): Tran kind: t.id, spec: { ...t, - topic: transformDataTopic(t.topic), + ...(t.topic !== undefined && { topic: transformDataTopic(t.topic) }), }, }; }); @@ -556,7 +608,7 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] for (const v of vars) { const commonProperties = { name: v.name, - label: v.label, + ...(v.label !== undefined && { label: v.label }), ...(v.description && { description: v.description }), skipUrlSync: Boolean(v.skipUrlSync), hide: transformVariableHideToEnum(v.hide), @@ -582,25 +634,27 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] kind: 'QueryVariable', spec: { ...commonProperties, - multi: Boolean(v.multi), - includeAll: Boolean(v.includeAll), + multi: v.multi ?? false, + includeAll: v.includeAll ?? false, ...(v.allValue && { allValue: v.allValue }), current: { value: v.current?.value, text: v.current?.text, }, - options: v.options || [], + options: v.options ?? [], + ...(v.definition && { definition: v.definition }), refresh: transformVariableRefreshToEnum(v.refresh), - ...(v.datasource && { datasource: v.datasource }), - regex: v.regex || '', - sort: transformSortVariableToEnum(v.sort), + regex: v.regex ?? '', + sort: v.sort ? transformSortVariableToEnum(v.sort) : 'disabled', query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: v.datasource?.type ?? getDefaultDatasourceType(), - datasource: { - name: v.datasource?.uid, - }, + ...(v.datasource?.uid && { + datasource: { + name: v.datasource.uid, + }, + }), spec: query, }, allowCustomValue: v.allowCustomValue ?? true, @@ -619,17 +673,17 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] kind: 'DatasourceVariable', spec: { ...commonProperties, - multi: Boolean(v.multi), - includeAll: Boolean(v.includeAll), + multi: v.multi ?? false, + includeAll: v.includeAll ?? false, ...(v.allValue && { allValue: v.allValue }), current: { value: v.current.value, text: v.current.text, }, - options: v.options || [], + options: v.options ?? [], refresh: transformVariableRefreshToEnum(v.refresh), pluginId, - regex: v.regex || '', + regex: v.regex ?? '', allowCustomValue: v.allowCustomValue ?? true, }, }; @@ -645,9 +699,9 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] value: v.current.value, text: v.current.text, }, - options: v.options, - multi: v.multi, - includeAll: v.includeAll, + options: v.options ?? [], + multi: v.multi ?? false, + includeAll: v.includeAll ?? false, ...(v.allValue && { allValue: v.allValue }), allowCustomValue: v.allowCustomValue ?? true, }, @@ -661,21 +715,23 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] const av: AdhocVariableKind = { kind: 'AdhocVariable', group: dsType, + ...(ds.uid && { + datasource: { + name: ds.uid, + }, + }), spec: { ...commonProperties, - baseFilters: validateFiltersOrigin(v.baseFilters) || [], - filters: validateFiltersOrigin(v.filters) || [], - defaultKeys: v.defaultKeys || [], + baseFilters: validateFiltersOrigin(v.baseFilters ?? []), + filters: validateFiltersOrigin(v.filters ?? []), + defaultKeys: + v.defaultKeys?.map((key: string | MetricFindValue) => + typeof key === 'string' ? { text: key, value: key } : key + ) ?? [], allowCustomValue: v.allowCustomValue ?? true, }, }; - if (ds.uid) { - av.datasource = { - name: ds.uid, - }; - } - variables.push(av); break; case 'constant': @@ -735,6 +791,11 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] const gb: GroupByVariableKind = { kind: 'GroupByVariable', group: dsType, + ...(ds.uid && { + datasource: { + name: ds.uid, + }, + }), spec: { ...commonProperties, options: v.options, @@ -746,12 +807,6 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] }, }; - if (ds.uid) { - gb.datasource = { - name: ds.uid, - }; - } - variables.push(gb); break; default: @@ -764,27 +819,34 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] function getAnnotations(annotations: AnnotationQuery[]): DashboardV2Spec['annotations'] { return annotations.map((a) => { + // Extract properties that are explicitly handled + const { name, enable, hide, iconColor, builtIn, datasource, target, filter, ...legacyOptions } = a; + const aq: AnnotationQueryKind = { kind: 'AnnotationQuery', spec: { - name: a.name, - ...(a.datasource && { datasource: a.datasource }), - enable: a.enable, - hide: Boolean(a.hide), - iconColor: a.iconColor, - builtIn: Boolean(a.builtIn), + name, + enable, + hide: Boolean(hide), + iconColor: iconColor, + builtIn: Boolean(builtIn), query: { kind: 'DataQuery', version: defaultDataQueryKind().version, - group: a.datasource?.type || getDefaultDatasourceType(), - datasource: { - name: a.datasource?.uid, - }, + group: datasource?.type || (builtIn ? 'grafana' : ''), + ...(datasource?.uid && { + datasource: { + name: datasource.uid, + }, + }), spec: { - ...a.target, + ...target, }, }, - filter: a.filter, + ...(filter !== undefined && { filter }), + + // Include any additional properties as legacyOptions + ...(Object.keys(legacyOptions).length > 0 && { legacyOptions }), }, }; return aq; @@ -808,7 +870,10 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] { case 'QueryVariable': const qv: VariableModel = { ...commonProperties, - current: v.spec.current, + current: { + text: v.spec.current.text, + value: v.spec.current.value, + }, options: v.spec.options, query: LEGACY_STRING_VALUE_KEY in v.spec.query.spec @@ -994,7 +1059,7 @@ function transformV2PanelToV1Panel( panel.links?.map((l) => ({ title: l.title, url: l.url, - ...(l.targetBlank && { targetBlank: l.targetBlank }), + ...(l.targetBlank !== undefined && { targetBlank: l.targetBlank }), })) || [], targets: panel.data.spec.queries.map((q) => { return { @@ -1009,17 +1074,27 @@ function transformV2PanelToV1Panel( }), transformations: panel.data.spec.transformations.map((t) => t.spec), gridPos, - cacheTimeout: panel.data.spec.queryOptions.cacheTimeout, - maxDataPoints: panel.data.spec.queryOptions.maxDataPoints, - interval: panel.data.spec.queryOptions.interval, - hideTimeOverride: panel.data.spec.queryOptions.hideTimeOverride, - queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL, - timeFrom: panel.data.spec.queryOptions.timeFrom, - timeShift: panel.data.spec.queryOptions.timeShift, - transparent: panel.transparent, - ...(repeat?.value && { repeat: repeat.value }), - ...(repeat?.direction && { repeatDirection: repeat.direction }), - ...(repeat?.maxPerRow && { maxPerRow: repeat.maxPerRow }), + ...(panel.data.spec.queryOptions.cacheTimeout !== undefined && { + cacheTimeout: panel.data.spec.queryOptions.cacheTimeout, + }), + ...(panel.data.spec.queryOptions.maxDataPoints !== undefined && { + maxDataPoints: panel.data.spec.queryOptions.maxDataPoints, + }), + ...(panel.data.spec.queryOptions.interval !== undefined && { interval: panel.data.spec.queryOptions.interval }), + ...(panel.data.spec.queryOptions.hideTimeOverride !== undefined && { + hideTimeOverride: panel.data.spec.queryOptions.hideTimeOverride, + }), + ...(panel.data.spec.queryOptions.queryCachingTTL !== undefined && { + queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL, + }), + ...(panel.data.spec.queryOptions.timeFrom !== undefined && { timeFrom: panel.data.spec.queryOptions.timeFrom }), + ...(panel.data.spec.queryOptions.timeShift !== undefined && { + timeShift: panel.data.spec.queryOptions.timeShift, + }), + ...(panel.transparent !== undefined && { transparent: panel.transparent }), + ...(repeat?.value !== undefined && { repeat: repeat.value }), + ...(repeat?.direction !== undefined && { repeatDirection: repeat.direction }), + ...(repeat?.maxPerRow !== undefined && { maxPerRow: repeat.maxPerRow }), }; } else if (p.kind === 'LibraryPanel') { const panel = p.spec; @@ -1054,7 +1129,7 @@ export function transformMappingsToV1(fieldConfig: FieldConfigSource): FieldConf ...fieldConfig.defaults, }; - if (fieldConfig.defaults.mappings) { + if (fieldConfig.defaults.mappings && fieldConfig.defaults.mappings.length > 0) { transformedDefaults.mappings = fieldConfig.defaults.mappings.map((mapping) => { switch (mapping.type) { case 'value': diff --git a/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts b/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts new file mode 100644 index 00000000000..d818e4953a3 --- /dev/null +++ b/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts @@ -0,0 +1,223 @@ +import { readdirSync, readFileSync } from 'fs'; +import path from 'path'; + +import { mockDataSource } from 'app/features/alerting/unified/mocks'; +import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; + +import { ensureV2Response } from './ResponseTransformers'; + +// Mock the config to provide datasource information +jest.mock('@grafana/runtime', () => { + const mockConfig = { + ...jest.requireActual('@grafana/runtime').config, + defaultDatasource: 'default-ds-uid', + datasources: { + 'default-ds-uid': { + meta: { id: 'prometheus' }, + name: 'default-ds-uid', + }, + 'non-default-test-ds-uid': { + meta: { id: 'loki' }, + name: 'non-default-test-ds-uid', + }, + 'existing-ref-uid': { + meta: { id: 'prometheus' }, + name: 'existing-ref-uid', + }, + 'existing-target-uid': { + meta: { id: 'elasticsearch' }, + name: 'existing-target-uid', + }, + 'existing-ref': { + meta: { id: 'prometheus' }, + name: 'existing-ref', + }, + '-- Mixed --': { + meta: { id: 'mixed' }, + name: '-- Mixed --', + }, + 'influx-uid': { + meta: { id: 'influxdb' }, + name: 'influx-uid', + }, + 'cloudwatch-uid': { + meta: { id: 'cloudwatch' }, + name: 'cloudwatch-uid', + }, + '-- Grafana --': { + meta: { id: 'grafana' }, + name: '-- Grafana --', + }, + }, + apps: {}, + featureToggles: { + dashboardScene: true, + kubernetesDashboards: true, + }, + }; + + return { + ...jest.requireActual('@grafana/runtime'), + config: mockConfig, + }; +}); + +/* + * Frontend Conversion Test Design Explanation: + * + * This test verifies that the frontend ensureV2Response function correctly converts + * dashboard data to v2beta1 format. The test uses input files from the backend test suite + * and compares the frontend conversion results with expected backend conversion results. + * + * Note: The frontend ensureV2Response function is designed to convert legacy dashboard + * format to v2 format, while the backend handles Kubernetes resource format conversions. + * This test focuses on verifying the frontend conversion logic works correctly. + */ + +// Set up the same datasources as backend test provider to ensure consistency +const dataSources = { + default: mockDataSource({ + name: 'default-ds-uid', + uid: 'default-ds-uid', + type: 'prometheus', + isDefault: true, + }), + nonDefault: mockDataSource({ + name: 'Non Default Test Datasource Name', + uid: 'non-default-test-ds-uid', + type: 'loki', + isDefault: false, + }), + existingRef: mockDataSource({ + name: 'Existing Ref Name', + uid: 'existing-ref-uid', + type: 'prometheus', + isDefault: false, + }), + existingTarget: mockDataSource({ + name: 'Existing Target Name', + uid: 'existing-target-uid', + type: 'elasticsearch', + isDefault: false, + }), + existingRefAlt: mockDataSource({ + name: 'Existing Ref Name', + uid: 'existing-ref', + type: 'prometheus', + isDefault: false, + }), + mixed: mockDataSource({ + name: MIXED_DATASOURCE_NAME, + uid: '-- Mixed --', + type: 'mixed', + isDefault: false, + }), + influx: mockDataSource({ + name: 'InfluxDB Test', + uid: 'influx-uid', + type: 'influxdb', + isDefault: false, + }), + cloudwatch: mockDataSource({ + name: 'CloudWatch Test', + uid: 'cloudwatch-uid', + type: 'cloudwatch', + isDefault: false, + }), + grafana: mockDataSource({ + name: '-- Grafana --', + uid: '-- Grafana --', + type: 'grafana', + isDefault: false, + }), +}; + +describe('Backend / Frontend result comparison', () => { + beforeEach(() => { + jest.clearAllMocks(); + setupDataSources(...Object.values(dataSources)); + + // Mock console methods to avoid test failures from expected warnings + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + const inputDir = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'apps', + 'dashboard', + 'pkg', + 'migration', + 'conversion', + 'testdata', + 'input' + ); + const outputDir = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'apps', + 'dashboard', + 'pkg', + 'migration', + 'conversion', + 'testdata', + 'output' + ); + + const jsonInputs = readdirSync(inputDir); + const LATEST_API_VERSION = 'dashboard.grafana.app/v2beta1'; + + // Filter to only process v1beta1 input files + const v1beta1Inputs = jsonInputs.filter((inputFile) => inputFile.startsWith('v1beta1.')); + + v1beta1Inputs.forEach((inputFile) => { + it(`should convert ${inputFile} spec to match backend conversion`, async () => { + const jsonInput = JSON.parse(readFileSync(path.join(inputDir, inputFile), 'utf8')); + + // Find the corresponding v2beta1 output file + const outputFileName = inputFile.replace('.json', `.${LATEST_API_VERSION.split('/')[1]}.json`); + const outputFilePath = path.join(outputDir, outputFileName); + + // Check if the expected output file exists + try { + const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); + expect(backendOutput.apiVersion).toBe(LATEST_API_VERSION); + + // Create dashboard models using frontend conversion + // ensureV2Response expects DashboardWithAccessInfo object + const frontendOutput = ensureV2Response({ + ...jsonInput, + kind: 'DashboardWithAccessInfo', + access: {}, + }); + + // Verify both outputs have valid spec structures + expect(frontendOutput.spec).toBeDefined(); + expect(backendOutput.spec).toBeDefined(); + + // Compare the spec structures + expect(backendOutput.spec).toEqual(frontendOutput.spec); + + // Verify the conversion doesn't throw errors and produces a valid structure + expect(() => JSON.stringify(frontendOutput)).not.toThrow(); + } catch (error) { + if (error instanceof Error && error.message.includes('ENOENT')) { + // Skip test if output file doesn't exist + console.warn(`Skipping test for ${inputFile} - no corresponding v2beta1 output file found`); + return; + } + throw error; + } + }); + }); +}); diff --git a/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts b/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts index 737fda747ab..0476e591ddb 100644 --- a/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts +++ b/public/app/features/dashboard/api/UnifiedDashboardAPI.test.ts @@ -89,12 +89,7 @@ describe('UnifiedDashboardAPI', () => { }; v1Client.getDashboardDTO.mockRejectedValue(new DashboardVersionError('v2beta1', 'Dashboard is V1 format')); - v2Client.getDashboardDTO.mockImplementation((params) => { - const actualClient = jest.requireActual('./v2').K8sDashboardV2API; - const client = new actualClient(); - return client.getDashboardDTO(params); - }); - mockBackendSrvGet = mockV2Response; + v2Client.getDashboardDTO.mockResolvedValue(mockV2Response as DashboardWithAccessInfo); const result = await api.getDashboardDTO('123'); expect(result).toEqual(mockV2Response); expect(v2Client.getDashboardDTO).toHaveBeenCalledWith('123'); diff --git a/public/app/features/dashboard/api/types.ts b/public/app/features/dashboard/api/types.ts index 5e358321a8f..950ace6061c 100644 --- a/public/app/features/dashboard/api/types.ts +++ b/public/app/features/dashboard/api/types.ts @@ -33,6 +33,7 @@ export interface DashboardWithAccessInfo extends Resource { }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValueOnce({ + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockResolvedValueOnce({ id: 1, uid: 'new-folder', title: 'New Folder', @@ -174,7 +175,7 @@ describe('v1 dashboard API', () => { }, }); jest - .spyOn(backendSrv, 'getFolderByUid') + .spyOn(folderHooks, 'getFolderByUidFacade') .mockRejectedValueOnce({ message: 'folder not found', status: 'not-found' }); const api = new K8sDashboardAPI(); @@ -186,7 +187,7 @@ describe('v1 dashboard API', () => { ...mockDashboardDto, metadata: { ...mockDashboardDto.metadata, annotations: { [AnnoKeyFolder]: 'new-folder' } }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); const api = new K8sDashboardAPI(); const dashboardDTO = await api.getDashboardDTO('test'); diff --git a/public/app/features/dashboard/api/v1.ts b/public/app/features/dashboard/api/v1.ts index 6f674511781..b8c57653923 100644 --- a/public/app/features/dashboard/api/v1.ts +++ b/public/app/features/dashboard/api/v1.ts @@ -2,7 +2,7 @@ import { locationUtil, UrlQueryMap } from '@grafana/data'; import { t } from '@grafana/i18n'; import { Dashboard } from '@grafana/schema'; import { Status } from '@grafana/schema/src/schema/dashboard/v2'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { getFolderByUidFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { getMessageFromError, getStatusFromError } from 'app/core/utils/errors'; import { ScopedResourceClient } from 'app/features/apiserver/client'; import { @@ -137,6 +137,7 @@ export class K8sDashboardAPI implements DashboardAPI { k8s: dash.metadata, version: dash.metadata.generation, created: dash.metadata.creationTimestamp, + publicDashboardEnabled: dash.access.isPublic, }, dashboard: { ...dash.spec, @@ -165,7 +166,7 @@ export class K8sDashboardAPI implements DashboardAPI { if (dash.metadata.annotations?.[AnnoKeyFolder]) { try { - const folder = await backendSrv.getFolderByUid(dash.metadata.annotations[AnnoKeyFolder]); + const folder = await getFolderByUidFacade(dash.metadata.annotations[AnnoKeyFolder]); result.meta.folderTitle = folder.title; result.meta.folderUrl = folder.url; result.meta.folderUid = folder.uid; diff --git a/public/app/features/dashboard/api/v2.test.ts b/public/app/features/dashboard/api/v2.test.ts index 8556b765702..58242c07c10 100644 --- a/public/app/features/dashboard/api/v2.test.ts +++ b/public/app/features/dashboard/api/v2.test.ts @@ -2,7 +2,7 @@ import { Spec as DashboardV2Spec, defaultSpec as defaultDashboardV2Spec, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; -import { backendSrv } from 'app/core/services/backend_srv'; +import * as folderHooks from 'app/api/clients/folder/v1beta1/hooks'; import { AnnoKeyFolder, AnnoKeyFolderTitle, @@ -101,7 +101,7 @@ describe('v2 dashboard API', () => { }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue({ + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockResolvedValue({ id: 1, uid: 'new-folder', title: 'New Folder', @@ -136,7 +136,7 @@ describe('v2 dashboard API', () => { }, }); jest - .spyOn(backendSrv, 'getFolderByUid') + .spyOn(folderHooks, 'getFolderByUidFacade') .mockRejectedValueOnce({ message: 'folder not found', status: 'not-found' }); const api = new K8sDashboardV2API(); @@ -148,7 +148,7 @@ describe('v2 dashboard API', () => { ...mockDashboardDto, metadata: { ...mockDashboardDto.metadata, annotations: { [AnnoKeyFolder]: 'new-folder' } }, }); - jest.spyOn(backendSrv, 'getFolderByUid').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); + jest.spyOn(folderHooks, 'getFolderByUidFacade').mockRejectedValueOnce({ message: 'folder not found', status: 403 }); const api = new K8sDashboardV2API(); const dashboardDTO = await api.getDashboardDTO('test'); diff --git a/public/app/features/dashboard/api/v2.ts b/public/app/features/dashboard/api/v2.ts index dfcbcc638f5..33ec2323786 100644 --- a/public/app/features/dashboard/api/v2.ts +++ b/public/app/features/dashboard/api/v2.ts @@ -2,7 +2,7 @@ import { locationUtil } from '@grafana/data'; import { t } from '@grafana/i18n'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { Status } from '@grafana/schema/src/schema/dashboard/v2'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { getFolderByUidFacade } from 'app/api/clients/folder/v1beta1/hooks'; import { getMessageFromError, getStatusFromError } from 'app/core/utils/errors'; import { ScopedResourceClient } from 'app/features/apiserver/client'; import { @@ -59,7 +59,7 @@ export class K8sDashboardV2API // load folder info if available if (dashboard.metadata.annotations && dashboard.metadata.annotations[AnnoKeyFolder]) { try { - const folder = await backendSrv.getFolderByUid(dashboard.metadata.annotations[AnnoKeyFolder]); + const folder = await getFolderByUidFacade(dashboard.metadata.annotations[AnnoKeyFolder]); dashboard.metadata.annotations[AnnoKeyFolderTitle] = folder.title; dashboard.metadata.annotations[AnnoKeyFolderUrl] = folder.url; } catch (e) { diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts index 359e16f5159..1ee2e118ae5 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts @@ -139,7 +139,6 @@ describe('panelEditor actions', () => { it('should not increment configRev when no changes made and leaving panel edit', async () => { const sourcePanel = new PanelModel({ id: 12, type: 'graph' }); sourcePanel.plugin = getPanelPlugin({}); - sourcePanel.plugin.angularPanelCtrl = undefined; const dashboard = createDashboardModelFixture({ panels: [{ id: 12, type: 'graph' }], @@ -169,7 +168,6 @@ describe('panelEditor actions', () => { it('should apply changes when dashboard was saved from panel edit', async () => { const sourcePanel = new PanelModel({ id: 12, type: 'graph' }); sourcePanel.plugin = getPanelPlugin({}); - sourcePanel.plugin.angularPanelCtrl = undefined; const dashboard = createDashboardModelFixture({ panels: [{ id: 12, type: 'graph' }], diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx index 61cc488109c..58d69d5ac92 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx @@ -24,6 +24,13 @@ jest.mock('@grafana/runtime', () => ({ })), }, reportInteraction: jest.fn(), + getDataSourceSrv: jest.fn(() => ({ + getInstanceSettings: jest.fn((uid: string) => ({ + uid, + name: 'Test Datasource', + type: 'prometheus', + })), + })), })); jest.mock('app/features/dashboard/utils/dashboard', () => ({ @@ -40,10 +47,16 @@ jest.mock('app/features/provisioning/hooks/useGetResourceRepositoryView', () => })), })); -jest.mock('../DashboardLibrary/DashboardLibrarySection', () => ({ - DashboardLibrarySection: () =>
Dashboard Library Section
, +jest.mock('../DashboardLibrary/api/dashboardLibraryApi', () => ({ + fetchProvisionedDashboards: jest.fn(() => Promise.resolve([])), + fetchCommunityDashboards: jest.fn(() => Promise.resolve({ page: 1, pages: 1, dashboards: [] })), + fetchCommunityDashboard: jest.fn(() => Promise.resolve({ json: {} })), })); +const mockFetchProvisionedDashboards = jest.mocked( + require('../DashboardLibrary/api/dashboardLibraryApi').fetchProvisionedDashboards +); + const mockUseGetResourceRepositoryView = jest.mocked( require('app/features/provisioning/hooks/useGetResourceRepositoryView').useGetResourceRepositoryView ); @@ -162,7 +175,7 @@ it('renders with buttons disabled when repository is read-only', () => { expect(screen.getByRole('button', { name: 'Add library panel' })).toBeDisabled(); }); -describe('DashboardLibrarySection feature toggle', () => { +describe('ProvisionedDashboardsEmptyPage feature toggle', () => { beforeEach(() => { jest.clearAllMocks(); mockUseGetResourceRepositoryView.mockReturnValue({ @@ -172,31 +185,41 @@ describe('DashboardLibrarySection feature toggle', () => { }); }); - it('renders DashboardLibrarySection when feature toggle is enabled and dashboardLibraryDatasourceUid param exists', () => { + it('renders ProvisionedDashboardsEmptyPage when feature toggle is enabled and dashboardLibraryDatasourceUid param exists', async () => { config.featureToggles.dashboardLibrary = true; mockSearchParams.set('dashboardLibraryDatasourceUid', 'test-uid'); + // Mock provisioned dashboards to return at least one dashboard so component renders + mockFetchProvisionedDashboards.mockResolvedValueOnce([ + { + uid: 'test-dashboard-1', + title: 'Test Dashboard', + pluginId: 'prometheus', + path: '/test/path', + }, + ]); + setup(); - expect(screen.getByTestId('dashboard-library-section')).toBeInTheDocument(); + expect(await screen.findByTestId('provisioned-dashboards-empty-page')).toBeInTheDocument(); }); - it('does not render DashboardLibrarySection when feature toggle is disabled', () => { + it('does not render ProvisionedDashboardsEmptyPage when feature toggle is disabled', () => { config.featureToggles.dashboardLibrary = false; mockSearchParams.delete('dashboardLibraryDatasourceUid'); setup(); - expect(screen.queryByTestId('dashboard-library-section')).not.toBeInTheDocument(); + expect(screen.queryByTestId('provisioned-dashboards-empty-page')).not.toBeInTheDocument(); }); - it('does not render DashboardLibrarySection when feature toggle is enabled but no dashboardLibraryDatasourceUid param', () => { + it('does not render ProvisionedDashboardsEmptyPage when feature toggle is enabled but no dashboardLibraryDatasourceUid param', () => { config.featureToggles.dashboardLibrary = true; mockSearchParams.delete('dashboardLibraryDatasourceUid'); setup(); - expect(screen.queryByTestId('dashboard-library-section')).not.toBeInTheDocument(); + expect(screen.queryByTestId('provisioned-dashboards-empty-page')).not.toBeInTheDocument(); }); }); @@ -233,15 +256,28 @@ describe('wrapperMaxWidth CSS class', () => { expect(wrapperElement).toHaveStyle('max-width: 890px'); }); - it('does not apply wrapperMaxWidth class when dashboardLibrary feature is enabled and dashboardLibraryDatasourceUid param exists', () => { + it('does not apply wrapperMaxWidth class when dashboardLibrary feature is enabled and dashboardLibraryDatasourceUid param exists', async () => { config.featureToggles.dashboardLibrary = true; mockSearchParams.set('dashboardLibraryDatasourceUid', 'test-uid'); + // Mock provisioned dashboards to return at least one dashboard so component renders + mockFetchProvisionedDashboards.mockResolvedValueOnce([ + { + uid: 'test-dashboard-1', + title: 'Test Dashboard', + pluginId: 'prometheus', + path: '/test/path', + }, + ]); + const { container } = render( ); + // Wait for ProvisionedDashboardsEmptyPage to render and complete async operations + await screen.findByTestId('provisioned-dashboards-empty-page'); + const wrapperElement = container.querySelector('[class*="dashboard-empty-wrapper"]'); expect(wrapperElement).toBeInTheDocument(); expect(wrapperElement).not.toHaveStyle('max-width: 890px'); diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx index 8e473124434..bef4318e339 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx @@ -10,7 +10,8 @@ import { Button, useStyles2, Text, Box, Stack, TextLink } from '@grafana/ui'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; -import { DashboardLibrarySection } from '../DashboardLibrary/DashboardLibrarySection'; +import { BasicProvisionedDashboardsEmptyPage } from '../DashboardLibrary/BasicProvisionedDashboardsEmptyPage'; +import { SuggestedDashboards } from '../DashboardLibrary/SuggestedDashboards'; import { DashboardEmptyExtensionPoint } from './DashboardEmptyExtensionPoint'; import { @@ -28,100 +29,115 @@ interface InternalProps { const InternalDashboardEmpty = ({ onAddVisualization, onAddLibraryPanel, onImportDashboard }: InternalProps) => { const styles = useStyles2(getStyles); - const [searchParams] = useSearchParams(); const dashboardLibraryDatasourceUid = searchParams.get('dashboardLibraryDatasourceUid'); return ( - -
- - - - - - Start your new dashboard by adding a visualization - - - - - - Select a data source and then query and visualize your data with charts, stats and tables or create - lists, markdowns and other widgets. + <> + +
+ + + + + + Start your new dashboard by adding a visualization - - - - - {config.featureToggles.dashboardLibrary && dashboardLibraryDatasourceUid && } - - - - - Import panel - - + - - Add visualizations that are shared with other dashboards. + + Select a data source and then query and visualize your data with charts, stats and tables or + create lists, markdowns and other widgets. - - - - Import a dashboard - - - - - Import dashboards from files or{' '} - - grafana.com - - . - + + {/* Suggested Dashboards Section */} + {config.featureToggles.suggestedDashboards && + config.featureToggles.dashboardLibrary && + dashboardLibraryDatasourceUid && } + + {/* Basic Provisioned Dashboards Section that don't include community dashboards */} + {config.featureToggles.dashboardLibrary && + !config.featureToggles.suggestedDashboards && + dashboardLibraryDatasourceUid && ( + + )} + + + + + + Import panel - - - - + + + + Add visualizations that are shared with other dashboards. + + + + + + + + + + Import a dashboard + + + + + Import dashboards from files or{' '} + + grafana.com + + . + + + + + + + - -
-
+
+
+ ); }; diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/BasicProvisionedDashboardsEmptyPage.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/BasicProvisionedDashboardsEmptyPage.tsx new file mode 100644 index 00000000000..91ed79019fe --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/BasicProvisionedDashboardsEmptyPage.tsx @@ -0,0 +1,182 @@ +import { css } from '@emotion/css'; +import { useState } from 'react'; +import { useAsync } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans } from '@grafana/i18n'; +import { getDataSourceSrv, locationService } from '@grafana/runtime'; +import { Button, useStyles2, Text, Box, Stack, Grid } from '@grafana/ui'; +import { PluginDashboard } from 'app/types/plugins'; + +import { DASHBOARD_LIBRARY_ROUTES } from '../types'; + +import { fetchProvisionedDashboards } from './api/dashboardLibraryApi'; +import { + CONTENT_KINDS, + CREATION_ORIGINS, + DashboardLibraryInteractions, + DISCOVERY_METHODS, + EVENT_LOCATIONS, + SOURCE_ENTRY_POINTS, +} from './interactions'; +import { getProvisionedDashboardImageUrl } from './utils/provisionedDashboardHelpers'; + +interface Props { + datasourceUid?: string; +} + +export const BasicProvisionedDashboardsEmptyPage = ({ datasourceUid }: Props) => { + const [showAll, setShowAll] = useState(false); + + const { value: templateDashboards } = useAsync(async (): Promise => { + if (!datasourceUid) { + return []; + } + + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + if (!ds) { + return []; + } + + const dashboards = await fetchProvisionedDashboards(ds.type); + return dashboards; + }, [datasourceUid]); + + const hasMoreThanThree = templateDashboards && templateDashboards.length > 3; + const dashboardsToShow = showAll ? templateDashboards : templateDashboards?.slice(0, 3); + + const styles = useStyles2(getStyles); + + const onImportDashboardClick = async (dashboard: PluginDashboard) => { + DashboardLibraryInteractions.itemClicked({ + contentKind: CONTENT_KINDS.DATASOURCE_DASHBOARD, + datasourceTypes: [dashboard.pluginId], + libraryItemId: dashboard.uid, + libraryItemTitle: dashboard.title, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.EMPTY_DASHBOARD, + discoveryMethod: DISCOVERY_METHODS.BROWSE, + }); + + const params = new URLSearchParams({ + datasource: datasourceUid || '', + title: dashboard.title || 'Template', + pluginId: dashboard.pluginId, + path: dashboard.path, + // tracking event purpose values + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + libraryItemId: dashboard.uid, + creationOrigin: CREATION_ORIGINS.DASHBOARD_LIBRARY_DATASOURCE_DASHBOARD, + }); + + const templateUrl = `${DASHBOARD_LIBRARY_ROUTES.Template}?${params.toString()}`; + locationService.push(templateUrl); + }; + + if (!templateDashboards?.length) { + return null; + } + + return ( + + + + + Start with a pre-made dashboard from your data source + + + + = 2 ? 2 : 1, + lg: (dashboardsToShow?.length || 1) >= 3 ? 3 : (dashboardsToShow?.length || 1) >= 2 ? 2 : 1, + }} + > + {dashboardsToShow?.map((dashboard, index) => { + // Use global index for consistent image assignment across pages + const imageUrl = getProvisionedDashboardImageUrl(index); + + return ( + + ); + }) || []} + + + {hasMoreThanThree && ( + + )} + + + ); +}; + +const TemplateDashboardBox = ({ + dashboard, + onImportClick, + index, + imageUrl, +}: { + dashboard: PluginDashboard; + onImportClick: (d: PluginDashboard) => void; + index: number; + imageUrl: string; +}) => { + const styles = useStyles2(getStyles); + return ( +
+ {dashboard.title} +
+ + {dashboard.title} + +
+ +
+ ); +}; + +function getStyles(theme: GrafanaTheme2) { + return { + templateDashboardBox: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + alignItems: 'center', + }), + templateDashboardTitle: css({ + flex: 1, + }), + templateDashboardImage: css({ + borderRadius: theme.shape.radius.default, + borderColor: theme.colors.text.primary, + borderWidth: 1, + borderStyle: 'solid', + objectFit: 'cover', + }), + showMoreButton: css({ + marginTop: theme.spacing(2), + }), + }; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/CommunityDashboardMappingForm.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/CommunityDashboardMappingForm.tsx new file mode 100644 index 00000000000..97c1e1d4de3 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/CommunityDashboardMappingForm.tsx @@ -0,0 +1,226 @@ +import { useEffect, useState } from 'react'; + +import { DataSourceInstanceSettings } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { getDataSourceSrv } from '@grafana/runtime'; +import { Stack, Text, Button, Alert, Field, Input, Box } from '@grafana/ui'; +import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; +import { DashboardInput, DataSourceInput } from 'app/features/manage-dashboards/state/reducers'; + +import { ContentKind, DashboardLibraryInteractions, EventLocation, SOURCE_ENTRY_POINTS } from './interactions'; +import { InputMapping, mapConstantInputs, mapUserSelectedDatasources } from './utils/autoMapDatasources'; + +interface Props { + unmappedDsInputs: DataSourceInput[]; + constantInputs: DashboardInput[]; + existingMappings: InputMapping[]; + onBack: () => void; + onPreview: (allMappings: InputMapping[]) => void; + dashboardName: string; + libraryItemId: string; + eventLocation: EventLocation; + contentKind: ContentKind; + datasourceTypes: string[]; +} + +interface UserSelectedDatasourceMappings { + name: string; + pluginId: string; + datasource: DataSourceInstanceSettings | undefined; +} + +export const CommunityDashboardMappingForm = ({ + unmappedDsInputs, + constantInputs, + existingMappings, + onBack, + onPreview, + dashboardName, + libraryItemId, + eventLocation, + contentKind, + datasourceTypes, +}: Props) => { + // Track mapping form shown on mount + useEffect(() => { + DashboardLibraryInteractions.mappingFormShown({ + contentKind, + datasourceTypes, + libraryItemId, + libraryItemTitle: dashboardName, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation, + unmappedDsInputsCount: unmappedDsInputs.length, + constantInputsCount: constantInputs.length, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const [userSelectedDsMappings, setUserSelectedDsMappings] = useState>( + () => { + // Initialize with existing unmapped inputs + return unmappedDsInputs.reduce>((acc, input) => { + const unmappedInput = { + name: input.name, + pluginId: input.pluginId, + datasource: undefined, + }; + acc[input.name] = unmappedInput; + return acc; + }, {}); + } + ); + + const [constantValues, setConstantValues] = useState>(() => { + // Initialize with default values from constantInputs + return constantInputs.reduce>((acc, input) => { + acc[input.name] = input.value; + return acc; + }, {}); + }); + + const onDatasourceSelect = (inputName: string, datasource: DataSourceInstanceSettings) => { + setUserSelectedDsMappings((prev) => ({ + ...prev, + [inputName]: { + ...prev[inputName], + datasource, + }, + })); + }; + + const onConstantChange = (inputName: string, value: string) => { + setConstantValues((prev) => ({ + ...prev, + [inputName]: value, + })); + }; + + const onPreviewClick = () => { + // Track mapping form completion + DashboardLibraryInteractions.mappingFormCompleted({ + contentKind, + datasourceTypes, + libraryItemId, + libraryItemTitle: dashboardName, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation, + userMappedCount: unmappedDsInputs.length, + autoMappedCount: existingMappings.length, + }); + + // Combine all mappings: + // 1. Existing auto-mapped datasources + // 2. User-selected datasources + // 3. Constant values (user-edited or defaults) + + const userSelectedDatasources = mapUserSelectedDatasources(unmappedDsInputs, userSelectedDsMappings); + + const constantMappings = mapConstantInputs(constantInputs, constantValues); + + const allMappings = [...existingMappings, ...userSelectedDatasources, ...constantMappings]; + onPreview(allMappings); + }; + + // Check if all unmapped datasource inputs have been mapped by user + // Constants are optional (have default values) + const allDatasourcesMapped = unmappedDsInputs.every((input) => userSelectedDsMappings[input.name]?.datasource); + + return ( + + + + + This dashboard requires datasource configuration. Select datasources for each input below. + + + + {existingMappings.length > 0 && ( + + + + + {{ count: existingMappings.length }} datasources were automatically configured: + + + + {existingMappings + .map((mapping) => { + const ds = getDataSourceSrv().getInstanceSettings(mapping.value); + return `${mapping.pluginId} → ${ds?.name || mapping.value}`; + }) + .join(' | ')} + + + + )} + + {unmappedDsInputs.length > 0 && ( + + + + Datasource Configuration + + + {unmappedDsInputs.map((input) => { + const selectedDatasource = userSelectedDsMappings[input.name]?.datasource; + + return ( + + onDatasourceSelect(input.name, ds)} + current={selectedDatasource?.uid} + noDefault={true} + placeholder={ + input.info || t('dashboard-library.community-mapping-select-datasource', 'Select a datasource') + } + pluginId={input.pluginId} + /> + + ); + })} + + )} + + {constantInputs.length > 0 && ( + + + Dashboard Variables + + {constantInputs.map((input) => ( + + onConstantChange(input.name, e.currentTarget.value)} + placeholder={input.value} + /> + + ))} + + )} + + + + + + + + + + ); +}; diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/CommunityDashboardSection.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/CommunityDashboardSection.tsx new file mode 100644 index 00000000000..2c70c354fbe --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/CommunityDashboardSection.tsx @@ -0,0 +1,299 @@ +import { css } from '@emotion/css'; +import { useEffect, useRef, useState } from 'react'; +import { useSearchParams } from 'react-router-dom-v5-compat'; +import { useAsync, useDebounce } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { getDataSourceSrv } from '@grafana/runtime'; +import { Button, useStyles2, Stack, Grid, EmptyState, Alert, Pagination, FilterInput } from '@grafana/ui'; + +import { DashboardCard } from './DashboardCard'; +import { MappingContext } from './SuggestedDashboardsModal'; +import { fetchCommunityDashboards } from './api/dashboardLibraryApi'; +import { + CONTENT_KINDS, + DashboardLibraryInteractions, + DISCOVERY_METHODS, + EVENT_LOCATIONS, + SOURCE_ENTRY_POINTS, +} from './interactions'; +import { GnetDashboard } from './types'; +import { + getThumbnailUrl, + getLogoUrl, + buildDashboardDetails, + onUseCommunityDashboard, +} from './utils/communityDashboardHelpers'; + +interface Props { + onShowMapping: (context: MappingContext) => void; + datasourceType?: string; +} + +// Constants for community dashboard pagination and API params +const COMMUNITY_PAGE_SIZE = 9; +const SEARCH_DEBOUNCE_MS = 500; +const DEFAULT_SORT_ORDER = 'downloads'; +const DEFAULT_SORT_DIRECTION = 'desc'; +const INCLUDE_LOGO = true; +const INCLUDE_SCREENSHOTS = true; + +export const CommunityDashboardSection = ({ onShowMapping, datasourceType }: Props) => { + const [searchParams] = useSearchParams(); + const datasourceUid = searchParams.get('dashboardLibraryDatasourceUid'); + const [currentPage, setCurrentPage] = useState(1); + const [searchQuery, setSearchQuery] = useState(''); + const hasTrackedLoaded = useRef(false); + + const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(''); + useDebounce( + () => { + setDebouncedSearchQuery(searchQuery); + }, + SEARCH_DEBOUNCE_MS, + [searchQuery] + ); + + // Reset to page 1 when debounced search query changes + useEffect(() => { + if (debouncedSearchQuery) { + setCurrentPage(1); + } + }, [debouncedSearchQuery]); + + const { + value: response, + loading, + error, + } = useAsync(async () => { + if (!datasourceUid) { + return null; + } + + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + if (!ds) { + return null; + } + + try { + const apiResponse = await fetchCommunityDashboards({ + orderBy: DEFAULT_SORT_ORDER, + direction: DEFAULT_SORT_DIRECTION, + page: currentPage, + pageSize: COMMUNITY_PAGE_SIZE, + includeLogo: INCLUDE_LOGO, + includeScreenshots: INCLUDE_SCREENSHOTS, + dataSourceSlugIn: ds.type, + filter: debouncedSearchQuery.trim() || undefined, + }); + + // Track search if query is present + if (debouncedSearchQuery.trim()) { + DashboardLibraryInteractions.searchPerformed({ + datasourceTypes: [ds.type], + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.MODAL_COMMUNITY_TAB, + hasResults: apiResponse.dashboards.length > 0, + resultCount: apiResponse.dashboards.length, + }); + } + + return { + dashboards: apiResponse.dashboards, + pages: apiResponse.pages, + datasourceType: ds.type, + }; + } catch (err) { + console.error('Error loading community dashboards', err); + throw err; + } + }, [datasourceUid, currentPage, debouncedSearchQuery]); + + // Track analytics only once on first successful load + useEffect(() => { + if (!loading && !hasTrackedLoaded.current && response?.dashboards && response.dashboards.length > 0) { + DashboardLibraryInteractions.loaded({ + numberOfItems: response.dashboards.length, + contentKinds: [CONTENT_KINDS.COMMUNITY_DASHBOARD], + datasourceTypes: [response.datasourceType], + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.MODAL_COMMUNITY_TAB, + }); + hasTrackedLoaded.current = true; + } + }, [loading, response]); + + const styles = useStyles2(getStyles); + + // Determine what to show in results area + const dashboards = Array.isArray(response?.dashboards) ? response.dashboards : []; + const totalPages = response?.pages || 1; + const showEmptyState = !loading && (!response?.dashboards || response.dashboards.length === 0); + const showError = !loading && error; + + const onPreviewCommunityDashboard = (dashboard: GnetDashboard) => { + if (!response) { + return; + } + + // Track item click + DashboardLibraryInteractions.itemClicked({ + contentKind: CONTENT_KINDS.COMMUNITY_DASHBOARD, + datasourceTypes: [response.datasourceType], + libraryItemId: String(dashboard.id), + libraryItemTitle: dashboard.name, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.MODAL_COMMUNITY_TAB, + discoveryMethod: debouncedSearchQuery.trim() ? DISCOVERY_METHODS.SEARCH : DISCOVERY_METHODS.BROWSE, + }); + + onUseCommunityDashboard({ + dashboard, + datasourceUid: datasourceUid || '', + datasourceType: response.datasourceType, + eventLocation: EVENT_LOCATIONS.MODAL_COMMUNITY_TAB, + onShowMapping, + }); + }; + + return ( + + + +
+ {loading ? ( + + {Array.from({ length: COMMUNITY_PAGE_SIZE }).map((_, i) => ( + + ))} + + ) : showError ? ( + + + + Failed to load community dashboards. Please try again. + + + + + ) : showEmptyState ? ( + window.open('https://grafana.com/grafana/dashboards/', '_blank')} + > + Browse Grafana.com + + } + > + {searchQuery && !datasourceType ? ( + + Try a different search term or browse more dashboards on Grafana.com. + + ) : ( + + Try a different search term or browse dashboards for different datasource types on Grafana.com. + + )} + + ) : ( + = 2 ? 2 : 1, + lg: dashboards.length >= 3 ? 3 : dashboards.length >= 2 ? 2 : 1, + }} + > + {dashboards.map((dashboard) => { + const thumbnailUrl = getThumbnailUrl(dashboard); + const logoUrl = getLogoUrl(dashboard); + const imageUrl = thumbnailUrl || logoUrl; + const isLogo = !thumbnailUrl; + const details = buildDashboardDetails(dashboard); + + return ( + onPreviewCommunityDashboard(dashboard)} + isLogo={isLogo} + details={details} + buttonText={Use dashboard} + /> + ); + })} + + )} +
+ {totalPages > 1 && ( +
+ +
+ )} +
+ ); +}; + +function getStyles(theme: GrafanaTheme2) { + return { + resultsContainer: css({ + width: '100%', + position: 'relative', + flex: 1, + overflow: 'auto', + }), + paginationWrapper: css({ + position: 'sticky', + bottom: 0, + backgroundColor: theme.colors.background.primary, + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'flex-end', + zIndex: 2, + }), + searchInput: css({ + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + }), + }; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardCard.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardCard.tsx new file mode 100644 index 00000000000..8d038e0e5c3 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardCard.tsx @@ -0,0 +1,263 @@ +import { css, cx } from '@emotion/css'; +import Skeleton from 'react-loading-skeleton'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { t, Trans } from '@grafana/i18n'; +import { Badge, Box, Button, Card, IconButton, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui'; +import { attachSkeleton, SkeletonComponent } from '@grafana/ui/unstable'; +import { PluginDashboard } from 'app/types/plugins'; + +import { GnetDashboard } from './types'; + +interface Details { + id: string; + datasource: string; + dependencies: string[]; + publishedBy: string; + lastUpdate: string; + grafanaComUrl?: string; +} + +interface Props { + title: string; + imageUrl?: string; + dashboard: PluginDashboard | GnetDashboard; + details?: Details; + onClick: () => void; + isLogo?: boolean; // Indicates if imageUrl is a small logo vs full screenshot + showDatasourceProvidedBadge?: boolean; + dimThumbnail?: boolean; // Apply 50% opacity to thumbnail when badge is shown + buttonText?: React.ReactNode; // Optional custom button text, defaults to "Use template" +} + +function DashboardCardComponent({ + title, + imageUrl, + onClick, + dashboard, + details, + isLogo, + showDatasourceProvidedBadge, + dimThumbnail, + buttonText, +}: Props) { + const styles = useStyles2(getStyles); + + return ( + + {title} +
+ {imageUrl ? ( + {title} { + console.error('Failed to load image for:', title, 'URL:', imageUrl); + e.currentTarget.style.display = 'none'; + }} + /> + ) : ( +
+ No preview available +
+ )} + {showDatasourceProvidedBadge && ( +
+ +
+ )} +
+
+ {dashboard.description && ( + {dashboard.description} + )} +
+ + + {details && ( + } placement="right"> + + + )} + +
+ ); +} + +function DetailsTooltipContent({ details }: { details: Details }) { + const Section = ({ label, value }: { label: string; value: string }) => { + return ( + + {label} + + {value} + + + ); + }; + + return ( + + +
+
+
+
+
+ {details.grafanaComUrl && ( + + + {t('dashboard-library.dashboard-card.details.view-on-grafana-com', 'View on Grafana.com')} + + + )} + + + ); +} + +function getStyles(theme: GrafanaTheme2) { + return { + card: css({ + gridTemplateAreas: ` + "Heading Heading" + "Thumbnail Thumbnail" + "Description Description" + "Actions Secondary"`, + gridTemplateRows: 'auto auto auto auto', + gridTemplateColumns: '1fr auto', + height: 'auto', + width: '350px', + background: 'transparent', + gridGap: theme.spacing(1), + }), + thumbnailContainer: css({ + gridArea: 'Thumbnail', + overflow: 'hidden', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: theme.shape.radius.default, + borderColor: theme.colors.border.strong, + borderWidth: 1, + borderStyle: 'solid', + width: '100%', + maxWidth: '350px', + height: '180px', + backgroundColor: theme.colors.background.canvas, + position: 'relative', + }), + thumbnail: css({ + width: '100%', + height: '100%', + objectFit: 'cover', + }), + logoContainer: css({ + gridArea: 'Thumbnail', + overflow: 'hidden', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: theme.shape.radius.default, + width: '100%', + height: '180px', + backgroundColor: theme.colors.background.secondary, + position: 'relative', + }), + logo: css({ + objectFit: 'fill', + }), + noImage: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: theme.colors.text.secondary, + fontSize: theme.typography.bodySmall.fontSize, + height: '100%', + width: '100%', + }), + descriptionWrapper: css({ + gridArea: 'Description', + wordBreak: 'break-word', + minHeight: `calc(${theme.typography.body.lineHeight} * 1em)`, // Preserve space even when empty + }), + title: css({ + display: '-webkit-box', + WebkitLineClamp: 1, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + textOverflow: 'ellipsis', + }), + description: css({ + display: '-webkit-box', + WebkitLineClamp: 1, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + textOverflow: 'ellipsis', + margin: 0, + height: `calc(${theme.typography.body.lineHeight} * 1em)`, // Fixed height for 1 line + }), + actionsContainer: css({ + marginTop: 0, + alignItems: 'stretch', + }), + detailsContainer: css({ + width: '340px', + }), + detailValue: css({ + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + }), + badgeContainer: css({ + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(1), + zIndex: 1, + }), + dimmedImage: css({ + opacity: 0.3, + }), + placeholderText: css({ + color: theme.colors.text.disabled, + fontStyle: 'italic', + }), + }; +} + +const DashboardCardSkeleton: SkeletonComponent = ({ rootProps }) => { + const styles = useStyles2(getSkeletonStyles); + return ; +}; + +const getSkeletonStyles = () => ({ + container: css({ + lineHeight: 1, + }), +}); + +export const DashboardCard = attachSkeleton(DashboardCardComponent, DashboardCardSkeleton); diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx index 266e671de03..a672d3eb04f 100644 --- a/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx @@ -1,31 +1,48 @@ import { css } from '@emotion/css'; -import { useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { useAsync } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; -import { getBackendSrv, getDataSourceSrv, locationService } from '@grafana/runtime'; -import { Button, useStyles2, Text, Box, Stack, Grid } from '@grafana/ui'; +import { Trans, t } from '@grafana/i18n'; +import { getDataSourceSrv, locationService } from '@grafana/runtime'; +import { useStyles2, Stack, Grid, Pagination, EmptyState, Button } from '@grafana/ui'; import { PluginDashboard } from 'app/types/plugins'; -import dashboardLibrary1 from 'img/dashboard-library/dashboard_library_1.jpg'; -import dashboardLibrary2 from 'img/dashboard-library/dashboard_library_2.jpg'; -import dashboardLibrary3 from 'img/dashboard-library/dashboard_library_3.jpg'; -import dashboardLibrary4 from 'img/dashboard-library/dashboard_library_4.jpg'; -import dashboardLibrary5 from 'img/dashboard-library/dashboard_library_5.jpg'; -import dashboardLibrary6 from 'img/dashboard-library/dashboard_library_6.jpg'; import { DASHBOARD_LIBRARY_ROUTES } from '../types'; -import { DashboardLibraryInteractions } from './interactions'; +import { DashboardCard } from './DashboardCard'; +import { fetchProvisionedDashboards } from './api/dashboardLibraryApi'; +import { + CONTENT_KINDS, + CREATION_ORIGINS, + DashboardLibraryInteractions, + DISCOVERY_METHODS, + EVENT_LOCATIONS, + SOURCE_ENTRY_POINTS, +} from './interactions'; +import { getProvisionedDashboardImageUrl } from './utils/provisionedDashboardHelpers'; + +// Constants for datasource-provided dashboards pagination +const PAGE_SIZE = 9; export const DashboardLibrarySection = () => { const [searchParams] = useSearchParams(); const datasourceUid = searchParams.get('dashboardLibraryDatasourceUid'); - const [showAll, setShowAll] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const hasTrackedLoaded = useRef(false); - const { value: templateDashboards } = useAsync(async (): Promise => { + // Get datasource info for empty state + const datasourceType = useMemo(() => { + if (!datasourceUid) { + return ''; + } + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + return ds?.type || ''; + }, [datasourceUid]); + + const { value: templateDashboards, loading } = useAsync(async (): Promise => { if (!datasourceUid) { return []; } @@ -35,38 +52,45 @@ export const DashboardLibrarySection = () => { return []; } - try { - const dashboards = await getBackendSrv().get(`api/plugins/${ds.type}/dashboards`, undefined, undefined, { - showErrorAlert: false, - }); - - if (dashboards.length > 0) { - DashboardLibraryInteractions.loaded({ - numberOfItems: dashboards.length, - contentKinds: ['datasource_dashboard'], - datasourceTypes: [ds.type], - sourceEntryPoint: 'datasource_page', - }); - } - return dashboards; - } catch (error) { - console.error('Error loading template dashboards', error); - return []; - } + const dashboards = await fetchProvisionedDashboards(ds.type); + return dashboards; }, [datasourceUid]); - const hasMoreThanThree = templateDashboards && templateDashboards.length > 3; - const dashboardsToShow = showAll ? templateDashboards : templateDashboards?.slice(0, 3); + // Track analytics only once on first successful load + useEffect(() => { + if (!loading && !hasTrackedLoaded.current && templateDashboards && templateDashboards.length > 0) { + DashboardLibraryInteractions.loaded({ + numberOfItems: templateDashboards.length, + contentKinds: [CONTENT_KINDS.DATASOURCE_DASHBOARD], + datasourceTypes: [datasourceType], + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.MODAL_PROVISIONED_TAB, + }); + hasTrackedLoaded.current = true; + } + }, [loading, templateDashboards, datasourceType]); - const styles = useStyles2(getStyles, dashboardsToShow?.length); + // Calculate pagination + const totalDashboards = templateDashboards?.length || 0; + const totalPages = Math.ceil(totalDashboards / PAGE_SIZE); + const startIndex = (currentPage - 1) * PAGE_SIZE; + const endIndex = startIndex + PAGE_SIZE; + const dashboardsToShow = templateDashboards?.slice(startIndex, endIndex); - const onImportDashboardClick = async (dashboard: PluginDashboard) => { + const styles = useStyles2(getStyles); + + // Determine what to show + const showEmptyState = !loading && (!templateDashboards || templateDashboards.length === 0); + + const onUseProvisionedDashboard = async (dashboard: PluginDashboard) => { DashboardLibraryInteractions.itemClicked({ - contentKind: 'datasource_dashboard', + contentKind: CONTENT_KINDS.DATASOURCE_DASHBOARD, datasourceTypes: [dashboard.pluginId], libraryItemId: dashboard.uid, libraryItemTitle: dashboard.title, - sourceEntryPoint: 'datasource_page', + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.MODAL_PROVISIONED_TAB, + discoveryMethod: DISCOVERY_METHODS.BROWSE, }); const params = new URLSearchParams({ @@ -75,126 +99,91 @@ export const DashboardLibrarySection = () => { pluginId: dashboard.pluginId, path: dashboard.path, // tracking event purpose values - sourceEntryPoint: 'datasource_page', + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, libraryItemId: dashboard.uid, - creationOrigin: 'dashboard_library_datasource_dashboard', + creationOrigin: CREATION_ORIGINS.DASHBOARD_LIBRARY_DATASOURCE_DASHBOARD, + eventLocation: EVENT_LOCATIONS.MODAL_PROVISIONED_TAB, + contentKind: CONTENT_KINDS.DATASOURCE_DASHBOARD, }); const templateUrl = `${DASHBOARD_LIBRARY_ROUTES.Template}?${params.toString()}`; locationService.push(templateUrl); }; - if (!templateDashboards?.length) { - return null; - } - return ( - - - - - Start with a pre-made dashboard from your data source + + {showEmptyState ? ( + window.open('https://grafana.com/grafana/plugins/', '_blank')}> + Browse plugins + + } + > + + Provisioned dashboards are provided by data source plugins. You can find more plugins on Grafana.com. - - - = 2 ? 2 : 1, - lg: (dashboardsToShow?.length || 1) >= 3 ? 3 : (dashboardsToShow?.length || 1) >= 2 ? 2 : 1, - }} - > - {dashboardsToShow?.map((dashboard, index) => ( - - )) || []} - - - {hasMoreThanThree && ( - - )} - - + + ) : ( + = 2 ? 2 : 1, + lg: loading ? 3 : (dashboardsToShow?.length || 1) >= 3 ? 3 : (dashboardsToShow?.length || 1) >= 2 ? 2 : 1, + }} + > + {loading && !templateDashboards + ? Array.from({ length: 9 }).map((_, i) => ) + : dashboardsToShow?.map((dashboard, index) => { + // Use global index for consistent image assignment across pages + const globalIndex = startIndex + index; + const imageUrl = getProvisionedDashboardImageUrl(globalIndex); + + return ( + onUseProvisionedDashboard(dashboard)} + buttonText={Use dashboard} + /> + ); + }) || []} + + )} + {!showEmptyState && totalPages > 1 && ( + setCurrentPage(page)} + className={styles.pagination} + /> + )} + ); }; -const TemplateDashboardBox = ({ - dashboard, - onImportClick, - index, -}: { - dashboard: PluginDashboard; - onImportClick: (d: PluginDashboard) => void; - index: number; -}) => { - const dashboardLibraryImages = [ - dashboardLibrary1, - dashboardLibrary2, - dashboardLibrary3, - dashboardLibrary4, - dashboardLibrary5, - dashboardLibrary6, - ]; - - const styles = useStyles2(getStyles); - return ( -
- {dashboard.title} -
- - {dashboard.title} - -
- -
- ); -}; - -function getStyles(theme: GrafanaTheme2, dashboardsLength?: number) { +function getStyles(theme: GrafanaTheme2) { return { - templateDashboardBox: css({ - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(1), + pagination: css({ + position: 'sticky', + bottom: 0, + backgroundColor: theme.colors.background.primary, + padding: theme.spacing(2), alignItems: 'center', - }), - templateDashboardTitle: css({ - flex: 1, - }), - templateDashboardImage: css({ - borderRadius: theme.shape.radius.default, - borderColor: theme.colors.text.primary, - borderWidth: 1, - borderStyle: 'solid', - objectFit: 'cover', - }), - showMoreButton: css({ - marginTop: theme.spacing(2), + zIndex: 2, }), }; } diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/SuggestedDashboards.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/SuggestedDashboards.tsx new file mode 100644 index 00000000000..8e0389525b0 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/SuggestedDashboards.tsx @@ -0,0 +1,394 @@ +import { css } from '@emotion/css'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { useSearchParams } from 'react-router-dom-v5-compat'; +import { useAsync } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { getDataSourceSrv, locationService } from '@grafana/runtime'; +import { Button, useStyles2, Grid } from '@grafana/ui'; +import { PluginDashboard } from 'app/types/plugins'; + +import { DashboardCard } from './DashboardCard'; +import { MappingContext, SuggestedDashboardsModal } from './SuggestedDashboardsModal'; +import { fetchCommunityDashboards, fetchProvisionedDashboards } from './api/dashboardLibraryApi'; +import { + CONTENT_KINDS, + CREATION_ORIGINS, + DashboardLibraryInteractions, + DISCOVERY_METHODS, + EVENT_LOCATIONS, + SOURCE_ENTRY_POINTS, +} from './interactions'; +import { GnetDashboard } from './types'; +import { + getThumbnailUrl, + getLogoUrl, + buildDashboardDetails, + onUseCommunityDashboard, +} from './utils/communityDashboardHelpers'; +import { getProvisionedDashboardImageUrl } from './utils/provisionedDashboardHelpers'; + +interface Props { + datasourceUid?: string; +} + +type MixedDashboard = + | { type: 'provisioned'; dashboard: PluginDashboard; index: number } + | { type: 'community'; dashboard: GnetDashboard }; + +type SuggestedDashboardsResult = { + dashboards: MixedDashboard[]; + hasMoreDashboards: boolean; +}; + +// Constants for suggested dashboards API params +const SUGGESTED_COMMUNITY_PAGE_SIZE = 2; +const DEFAULT_SORT_ORDER = 'downloads'; +const DEFAULT_SORT_DIRECTION = 'desc'; +const INCLUDE_SCREENSHOTS = true; +const INCLUDE_LOGO = true; + +export const SuggestedDashboards = ({ datasourceUid }: Props) => { + const styles = useStyles2(getStyles); + const hasTrackedLoaded = useRef(false); + const [searchParams, setSearchParams] = useSearchParams(); + const showLibraryModal = searchParams.get('dashboardLibraryModal') === 'open'; + + // Validate and get default tab from URL params + const tabParam = searchParams.get('dashboardLibraryTab'); + const defaultTab: 'datasource' | 'community' = tabParam === 'community' ? 'community' : 'datasource'; + + const [mappingContext, setMappingContext] = useState(null); + + // Get datasource type for dynamic title + const datasourceType = useMemo(() => { + if (!datasourceUid) { + return ''; + } + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + return ds?.type || ''; + }, [datasourceUid]); + + const { value: result, loading } = useAsync(async (): Promise => { + if (!datasourceUid) { + return { dashboards: [], hasMoreDashboards: false }; + } + + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + if (!ds) { + return { dashboards: [], hasMoreDashboards: false }; + } + + try { + // Fetch both provisioned and community dashboards in parallel + const [provisioned, communityResponse] = await Promise.all([ + // Fetch provisioned dashboards + fetchProvisionedDashboards(ds.type), + + // Fetch community dashboards + fetchCommunityDashboards({ + orderBy: DEFAULT_SORT_ORDER, + direction: DEFAULT_SORT_DIRECTION, + page: 1, + pageSize: SUGGESTED_COMMUNITY_PAGE_SIZE, + includeScreenshots: INCLUDE_SCREENSHOTS, + dataSourceSlugIn: ds.type, + includeLogo: INCLUDE_LOGO, + }), + ]); + + const community = communityResponse.dashboards; + + // Mix: 1 provisioned + 2 community + const mixed: MixedDashboard[] = []; + + // Take 1 provisioned if available + if (provisioned.length > 0) { + mixed.push({ type: 'provisioned', dashboard: provisioned[0], index: 0 }); + } + + // Take up to 2 community dashboards + const communityCount = Math.min(2, community.length); + for (let i = 0; i < communityCount; i++) { + mixed.push({ type: 'community', dashboard: community[i] }); + } + + // Fill remaining slots if we have less than 3 + while (mixed.length < 3) { + const provisionedUsed = mixed.filter((m) => m.type === 'provisioned').length; + const communityUsed = mixed.filter((m) => m.type === 'community').length; + + if (provisionedUsed < provisioned.length) { + mixed.push({ type: 'provisioned', dashboard: provisioned[provisionedUsed], index: provisionedUsed }); + } else if (communityUsed < community.length) { + mixed.push({ type: 'community', dashboard: community[communityUsed] }); + } else { + break; // Not enough dashboards + } + } + + // Determine if there are more dashboards available beyond what we're showing + // Show "View all" if: more than 1 provisioned exists OR we got the full page size of community dashboards + const hasMoreDashboards = provisioned.length > 1 || community.length >= SUGGESTED_COMMUNITY_PAGE_SIZE; + + return { dashboards: mixed, hasMoreDashboards }; + } catch (error) { + console.error('Error loading suggested dashboards', error); + return { dashboards: [], hasMoreDashboards: false }; + } + }, [datasourceUid]); + + // Determine which tab should be default based on available data + const computedDefaultTab = useMemo((): 'datasource' | 'community' => { + if (!result || loading) { + return 'datasource'; // Default while loading + } + + const hasProvisioned = result.dashboards.some((d) => d.type === 'provisioned'); + + // Prefer datasource tab if it has data, otherwise community + return hasProvisioned ? 'datasource' : 'community'; + }, [result, loading]); + + // Track analytics only once on first successful load + useEffect(() => { + if (!loading && !hasTrackedLoaded.current && result && result.dashboards.length > 0) { + const contentKinds = [ + ...new Set( + result.dashboards.map((m) => + m.type === 'provisioned' ? CONTENT_KINDS.DATASOURCE_DASHBOARD : CONTENT_KINDS.COMMUNITY_DASHBOARD + ) + ), + ]; + + DashboardLibraryInteractions.loaded({ + numberOfItems: result.dashboards.length, + contentKinds, + datasourceTypes: [datasourceType], + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.EMPTY_DASHBOARD, + }); + hasTrackedLoaded.current = true; + } + }, [loading, result, datasourceType]); + + const onModalDismiss = () => { + // Remove modal-related query params while keeping datasourceUid + setSearchParams((params) => { + params.delete('dashboardLibraryModal'); + params.delete('dashboardLibraryTab'); + return params; + }); + setMappingContext(null); + }; + + const onOpenModal = (tab: 'datasource' | 'community') => { + setSearchParams((params) => { + const newParams = new URLSearchParams(params); + newParams.set('dashboardLibraryModal', 'open'); + newParams.set('dashboardLibraryTab', tab); + return newParams; + }); + }; + + const onShowMapping = (context: MappingContext) => { + setMappingContext(context); + onOpenModal(computedDefaultTab); + }; + + const onUseProvisionedDashboard = (dashboard: PluginDashboard) => { + if (!datasourceUid) { + return; + } + + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + if (!ds) { + return; + } + + DashboardLibraryInteractions.itemClicked({ + contentKind: CONTENT_KINDS.DATASOURCE_DASHBOARD, + datasourceTypes: [ds.type], + libraryItemId: dashboard.uid, + libraryItemTitle: dashboard.title, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.EMPTY_DASHBOARD, + discoveryMethod: DISCOVERY_METHODS.BROWSE, + }); + + // Navigate to template route (existing flow) + const params = new URLSearchParams({ + datasource: datasourceUid, + title: dashboard.title || 'Template', + pluginId: dashboard.pluginId, + path: dashboard.path, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + libraryItemId: dashboard.uid, + creationOrigin: CREATION_ORIGINS.DASHBOARD_LIBRARY_DATASOURCE_DASHBOARD, + eventLocation: EVENT_LOCATIONS.EMPTY_DASHBOARD, + contentKind: CONTENT_KINDS.DATASOURCE_DASHBOARD, + }); + + locationService.push(`/dashboard/template?${params.toString()}`); + }; + + const onPreviewCommunityDashboard = (dashboard: GnetDashboard) => { + if (!datasourceUid) { + return; + } + + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + if (!ds) { + return; + } + + // Track item click + DashboardLibraryInteractions.itemClicked({ + contentKind: CONTENT_KINDS.COMMUNITY_DASHBOARD, + datasourceTypes: [ds.type], + libraryItemId: String(dashboard.id), + libraryItemTitle: dashboard.name, + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + eventLocation: EVENT_LOCATIONS.EMPTY_DASHBOARD, + discoveryMethod: DISCOVERY_METHODS.BROWSE, + }); + + onUseCommunityDashboard({ + dashboard, + datasourceUid, + datasourceType: ds.type, + eventLocation: EVENT_LOCATIONS.EMPTY_DASHBOARD, + onShowMapping: onShowMapping, + }); + }; + + // Don't render if no dashboards or still loading + if (!loading && (!result || result.dashboards.length === 0)) { + return null; + } + + return ( + <> +
+
+
+

+ {datasourceType + ? t( + 'dashboard-library.suggested-dashboards-title-with-datasource', + 'Build a dashboard using suggested options for your {{datasourceType}} data source', + { datasourceType } + ) + : t( + 'dashboard-library.suggested-dashboards-title', + 'Build a dashboard using suggested options for your selected data source' + )} +

+

+ + Browse and select from data-source provided or community dashboards + +

+
+ {result?.hasMoreDashboards && ( + + )} +
+ + + {loading + ? Array.from({ length: 3 }).map((_, i) => ) + : result?.dashboards.map((item, idx) => { + if (item.type === 'provisioned') { + return ( + onUseProvisionedDashboard(item.dashboard)} + showDatasourceProvidedBadge={true} + dimThumbnail={true} + buttonText={Use dashboard} + /> + ); + } else { + const thumbnailUrl = getThumbnailUrl(item.dashboard); + const imageUrl = thumbnailUrl || getLogoUrl(item.dashboard); + const isLogo = !thumbnailUrl; + const details = buildDashboardDetails(item.dashboard); + + return ( + onPreviewCommunityDashboard(item.dashboard)} + isLogo={isLogo} + details={details} + buttonText={Use dashboard} + /> + ); + } + }) || []} + +
+ + + ); +}; + +function getStyles(theme: GrafanaTheme2) { + return { + container: css({ + borderRadius: theme.shape.radius.default, + borderColor: theme.colors.border.strong, + borderStyle: 'dashed', + borderWidth: 1, + padding: theme.spacing(4), + }), + header: css({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: theme.spacing(1), + gap: theme.spacing(2), + paddingRight: theme.spacing(2), + paddingLeft: theme.spacing(2), + }), + headerText: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(0.5), + flex: 1, + }), + title: css({ + margin: 0, + fontSize: theme.typography.h2.fontSize, + fontWeight: theme.typography.fontWeightMedium, + lineHeight: theme.typography.h2.lineHeight, + }), + subtitle: css({ + margin: 0, + fontSize: theme.typography.body.fontSize, + color: theme.colors.text.secondary, + lineHeight: theme.typography.body.lineHeight, + }), + }; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/SuggestedDashboardsModal.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/SuggestedDashboardsModal.tsx new file mode 100644 index 00000000000..5e1de6cf4dc --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/SuggestedDashboardsModal.tsx @@ -0,0 +1,205 @@ +import { css } from '@emotion/css'; +import { useState, useEffect, useMemo } from 'react'; +import { useSearchParams } from 'react-router-dom-v5-compat'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { getDataSourceSrv } from '@grafana/runtime'; +import { Modal, TabsBar, Tab, TabContent, useStyles2, Text } from '@grafana/ui'; +import { DashboardInput, DataSourceInput } from 'app/features/manage-dashboards/state/reducers'; +import { DashboardJson } from 'app/features/manage-dashboards/types'; + +import { CommunityDashboardMappingForm } from './CommunityDashboardMappingForm'; +import { CommunityDashboardSection } from './CommunityDashboardSection'; +import { DashboardLibrarySection } from './DashboardLibrarySection'; +import { ContentKind, EventLocation } from './interactions'; +import { InputMapping } from './utils/autoMapDatasources'; + +interface SuggestedDashboardsModalProps { + isOpen: boolean; + onDismiss: () => void; + initialMappingContext?: MappingContext | null; + defaultTab?: 'datasource' | 'community'; +} + +type ModalView = 'datasource' | 'community' | 'mapping'; + +export interface MappingContext { + dashboardName: string; + dashboardJson: DashboardJson; + unmappedDsInputs: DataSourceInput[]; + constantInputs: DashboardInput[]; + existingMappings: InputMapping[]; + onInterpolateAndNavigate: (mappings: InputMapping[]) => void; + // Tracking context for analytics + eventLocation: EventLocation; + contentKind: ContentKind; +} + +export const SuggestedDashboardsModal = ({ + isOpen, + onDismiss, + initialMappingContext, + defaultTab = 'datasource', +}: SuggestedDashboardsModalProps) => { + const [searchParams, setSearchParams] = useSearchParams(); + const datasourceUid = searchParams.get('dashboardLibraryDatasourceUid'); + + const [activeView, setActiveView] = useState(initialMappingContext ? 'mapping' : defaultTab); + const [mappingContext, setMappingContext] = useState(initialMappingContext || null); + const styles = useStyles2(getStyles); + + // Get datasource info for modal title and search + const datasourceInfo = useMemo(() => { + if (!datasourceUid) { + return { type: '' }; + } + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + return { + type: ds?.type || '', + }; + }, [datasourceUid]); + + // Update state when initialMappingContext changes or modal opens/closes + useEffect(() => { + if (initialMappingContext) { + setMappingContext(initialMappingContext); + setActiveView('mapping'); + } else if (isOpen) { + // When modal opens, set to defaultTab + setActiveView(defaultTab); + } else { + // Reset when modal closes + setMappingContext(null); + } + }, [initialMappingContext, isOpen, defaultTab]); + + const onTabChange = (tab: 'datasource' | 'community') => { + setActiveView(tab); + // Update URL to reflect current tab + setSearchParams((params) => { + const newParams = new URLSearchParams(params); + newParams.set('dashboardLibraryTab', tab); + return newParams; + }); + }; + + const handleShowMapping = (context: MappingContext) => { + setMappingContext(context); + setActiveView('mapping'); + }; + + const handleBackToDashboards = () => { + setMappingContext(null); + setActiveView('community'); + }; + + return ( + + {activeView !== 'mapping' && ( +
+ + + Browse and select from data-source provided or community dashboards + + + + + onTabChange('datasource')} + /> + onTabChange('community')} + /> + +
+ )} + + + {activeView === 'datasource' && } + {activeView === 'community' && ( + + )} + {activeView === 'mapping' && mappingContext && ( + { + mappingContext.onInterpolateAndNavigate(allMappings); + }} + dashboardName={mappingContext.dashboardName} + libraryItemId={String(mappingContext.dashboardJson.gnetId || '')} + eventLocation={mappingContext.eventLocation} + contentKind={mappingContext.contentKind} + datasourceTypes={[datasourceInfo.type]} + /> + )} + +
+ ); +}; + +function getStyles(theme: GrafanaTheme2) { + return { + modal: css({ + width: '90%', + maxWidth: '1200px', + height: '80vh', + display: 'flex', + flexDirection: 'column', + }), + modalContent: css({ + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + padding: 0, + marginBottom: 0, + height: '100%', + }), + stickyHeader: css({ + position: 'sticky', + top: 0, + zIndex: 2, + backgroundColor: theme.colors.background.primary, + paddingTop: theme.spacing(3), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + }), + tabContent: css({ + flex: 1, + overflow: 'auto', + paddingTop: theme.spacing(3), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), + }), + }; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/api/dashboardLibraryApi.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/api/dashboardLibraryApi.ts new file mode 100644 index 00000000000..b97568a8ab9 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/api/dashboardLibraryApi.ts @@ -0,0 +1,111 @@ +import { getBackendSrv } from '@grafana/runtime'; +import { DashboardJson } from 'app/features/manage-dashboards/types'; +import { PluginDashboard } from 'app/types/plugins'; + +import { GnetDashboardsResponse, Link } from '../types'; + +/** + * Parameters for fetching community dashboards from Grafana.com + */ +export interface FetchCommunityDashboardsParams { + orderBy: string; + direction: 'asc' | 'desc'; + page: number; + pageSize: number; + includeLogo: boolean; + includeScreenshots: boolean; + dataSourceSlugIn?: string; + filter?: string; +} + +/** + * Dependency item from Grafana.com dashboard API + */ +export interface GnetDashboardDependency { + pluginSlug: string; + pluginTypeCode: 'app' | 'panel' | 'datasource' | 'grafana'; + pluginName?: string; + pluginVersion?: string; + [key: string]: unknown; +} + +/** + * Response from the Gnet API when fetching a single dashboard + */ +export interface GnetDashboardResponse { + json: DashboardJson; + dependencies?: { + items?: GnetDashboardDependency[]; + direction?: 'asc' | 'desc'; + orderBy?: string; + links?: Link[]; + }; + [key: string]: unknown; +} + +/** + * Fetch community dashboards from Grafana.com + */ +export async function fetchCommunityDashboards( + params: FetchCommunityDashboardsParams +): Promise { + const searchParams = new URLSearchParams({ + orderBy: params.orderBy, + direction: params.direction, + page: params.page.toString(), + pageSize: params.pageSize.toString(), + includeLogo: params.includeLogo ? '1' : '0', + includeScreenshots: params.includeScreenshots ? 'true' : 'false', + }); + + if (params.dataSourceSlugIn) { + searchParams.append('dataSourceSlugIn', params.dataSourceSlugIn); + } + if (params.filter) { + searchParams.append('filter', params.filter); + } + + const result = await getBackendSrv().get(`/api/gnet/dashboards?${searchParams}`, undefined, undefined, { + showErrorAlert: false, + }); + + // Grafana.com API returns format: { page: number, pages: number, items: GnetDashboard[] } + // We normalize it to use "dashboards" instead of "items" for consistency + if (result && Array.isArray(result.items)) { + return { + page: result.page || params.page, + pages: result.pages || 1, + dashboards: result.items, + }; + } + + // Fallback for unexpected response format + console.warn('Unexpected API response format from Grafana.com:', result); + return { + page: params.page, + pages: 1, + dashboards: [], + }; +} + +/** + * Fetch a single community dashboard's full JSON from Grafana.com + */ +export async function fetchCommunityDashboard(gnetId: number): Promise { + return getBackendSrv().get(`/api/gnet/dashboards/${gnetId}`); +} + +/** + * Fetch provisioned dashboards for a datasource type + */ +export async function fetchProvisionedDashboards(datasourceType: string): Promise { + try { + const dashboards = await getBackendSrv().get(`api/plugins/${datasourceType}/dashboards`, undefined, undefined, { + showErrorAlert: false, + }); + return Array.isArray(dashboards) ? dashboards : []; + } catch (error) { + console.error('Error loading provisioned dashboards', error); + return []; + } +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts index 1222007dd84..714e53145e9 100644 --- a/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts @@ -2,13 +2,40 @@ import { reportInteraction } from '@grafana/runtime'; const SCHEMA_VERSION = 1; -type ContentKind = 'datasource_dashboard'; -// in future this could be "template_dashboard" if/when items become templates or "community_dashboard" -// | 'template_dashboard' | 'community_dashboard'; +// Constant values for tracking events +export const EVENT_LOCATIONS = { + EMPTY_DASHBOARD: 'empty_dashboard', + MODAL_PROVISIONED_TAB: 'suggested_dashboards_modal_provisioned_tab', + MODAL_COMMUNITY_TAB: 'suggested_dashboards_modal_community_tab', +} as const; -type SourceEntryPoint = 'datasource_page'; -// possible future flows onboarding, create-dashboard, empty states -// | 'create_dashboard' | 'empty_state'; +export const CONTENT_KINDS = { + DATASOURCE_DASHBOARD: 'datasource_dashboard', + COMMUNITY_DASHBOARD: 'community_dashboard', + // in future this could also include "TEMPLATE_DASHBOARD" if/when items become templates +} as const; + +export const SOURCE_ENTRY_POINTS = { + DATASOURCE_PAGE: 'datasource_page', + // possible future flows: CREATE_DASHBOARD, EMPTY_STATE +} as const; + +export const DISCOVERY_METHODS = { + SEARCH: 'search', + BROWSE: 'browse', +} as const; + +export const CREATION_ORIGINS = { + DASHBOARD_LIBRARY_DATASOURCE_DASHBOARD: 'dashboard_library_datasource_dashboard', + DASHBOARD_LIBRARY_COMMUNITY_DASHBOARD: 'dashboard_library_community_dashboard', +} as const; + +// Derive types from constant maps for single source of truth +export type EventLocation = (typeof EVENT_LOCATIONS)[keyof typeof EVENT_LOCATIONS]; +export type ContentKind = (typeof CONTENT_KINDS)[keyof typeof CONTENT_KINDS]; +export type SourceEntryPoint = (typeof SOURCE_ENTRY_POINTS)[keyof typeof SOURCE_ENTRY_POINTS]; +export type DiscoveryMethod = (typeof DISCOVERY_METHODS)[keyof typeof DISCOVERY_METHODS]; +export type CreationOrigin = (typeof CREATION_ORIGINS)[keyof typeof CREATION_ORIGINS]; export const DashboardLibraryInteractions = { loaded: (properties: { @@ -16,18 +43,54 @@ export const DashboardLibraryInteractions = { contentKinds: ContentKind[]; datasourceTypes: string[]; sourceEntryPoint: SourceEntryPoint; + eventLocation: EventLocation; }) => { reportDashboardLibraryInteraction('loaded', properties); }, + searchPerformed: (properties: { + datasourceTypes: string[]; + sourceEntryPoint: SourceEntryPoint; + eventLocation: EventLocation; + hasResults: boolean; + resultCount: number; + }) => { + reportDashboardLibraryInteraction('search_performed', properties); + }, itemClicked: (properties: { contentKind: ContentKind; datasourceTypes: string[]; libraryItemId: string; libraryItemTitle: string; sourceEntryPoint: SourceEntryPoint; + eventLocation: EventLocation; + discoveryMethod: DiscoveryMethod; }) => { reportDashboardLibraryInteraction('item_clicked', properties); }, + mappingFormShown: (properties: { + contentKind: ContentKind; + datasourceTypes: string[]; + libraryItemId: string; + libraryItemTitle: string; + sourceEntryPoint: SourceEntryPoint; + eventLocation: EventLocation; + unmappedDsInputsCount: number; + constantInputsCount: number; + }) => { + reportDashboardLibraryInteraction('mapping_form_shown', properties); + }, + mappingFormCompleted: (properties: { + contentKind: ContentKind; + datasourceTypes: string[]; + libraryItemId: string; + libraryItemTitle: string; + sourceEntryPoint: SourceEntryPoint; + eventLocation: EventLocation; + userMappedCount: number; + autoMappedCount: number; + }) => { + reportDashboardLibraryInteraction('mapping_form_completed', properties); + }, }; const reportDashboardLibraryInteraction = (name: string, properties?: Record) => { diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/types.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/types.ts new file mode 100644 index 00000000000..38267a4870f --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/types.ts @@ -0,0 +1,48 @@ +import { DashboardJson } from 'app/features/manage-dashboards/types'; + +export interface Link { + rel: string; + href: string; +} + +export interface Screenshot { + links: Link[]; +} + +export interface LogoImage { + content: string; + filename: string; + type: string; +} + +export interface Logo { + small?: LogoImage; + large?: LogoImage; +} + +export interface GnetDashboard { + id: number; + uid: string; + name: string; + description: string; + downloads: number; + datasource: string; + screenshots?: Screenshot[]; + logos?: Logo; + json?: DashboardJson; // Full dashboard JSON from detail API + createdAt?: string; // ISO date string if available + updatedAt?: string; // ISO date string if available + publishedAt?: string; // ISO date string if available + // Author/organization information + orgId?: number; + orgName?: string; + orgSlug?: string; + userId?: number; + userName?: string; +} + +export interface GnetDashboardsResponse { + page: number; + pages: number; + dashboards: GnetDashboard[]; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/autoMapDatasources.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/autoMapDatasources.ts new file mode 100644 index 00000000000..20084231e61 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/autoMapDatasources.ts @@ -0,0 +1,152 @@ +import { getDataSourceSrv } from '@grafana/runtime'; +import { Input } from 'app/features/dashboard/components/DashExportModal/DashboardExporter'; +import { DashboardInput, DataSourceInput, InputType } from 'app/features/manage-dashboards/state/reducers'; + +export interface InputMapping { + name: string; + type: 'datasource' | 'constant'; + pluginId?: string; + value: string; +} + +/** + * Type guard to check if an Input is a DataSourceInput. + * DataSourceInput requires both type='datasource' and a pluginId property. + */ +export function isDataSourceInput(input: Input): input is Input & DataSourceInput { + return input.type === 'datasource' && 'pluginId' in input; +} + +export interface AutoMapResult { + allMapped: boolean; + mappings: InputMapping[]; + unmappedDsInputs: DataSourceInput[]; +} + +/** + * Attempts to automatically map datasource inputs to available datasources. + * Uses two ways of mapping: + * 1. Prefer the current datasource if it matches the required type + * 2. Auto-select if only one compatible datasource exists + * + * @param inputs - Array of datasource inputs from dashboard __inputs + * @param currentDatasourceUid - UID of the datasource selected in "build dashboard" flow + * @returns Result containing mappings, unmapped inputs, and whether all inputs were mapped + */ +export function tryAutoMapDatasources(inputs: DataSourceInput[], currentDatasourceUid: string): AutoMapResult { + const mappings: InputMapping[] = []; + const unmappedDsInputs: DataSourceInput[] = []; + + for (const input of inputs) { + // Get all datasources compatible with this input's plugin type + const compatibleDs = getDataSourceSrv() + .getList({ type: input.pluginId }) + .filter((ds) => ds.uid); + + let selectedDs: string | undefined; + + // Option 1: Use current datasource if compatible + if (compatibleDs.some((ds) => ds.uid === currentDatasourceUid)) { + selectedDs = currentDatasourceUid; + } + // Option 2: Auto-select if only one option exists AND it's not the current datasource's type + // (example: only auto-select if we are confident it's the right choice) + else if (compatibleDs.length === 1) { + const currentDs = getDataSourceSrv().getInstanceSettings(currentDatasourceUid); + + // Only auto-select if: + // - The single option matches the input's plugin type exactly + // - OR we're coming from a datasource of the same type (e.g., Prometheus -> Prometheus) + if (currentDs && currentDs.type === input.pluginId) { + selectedDs = compatibleDs[0].uid; + } + } + + if (selectedDs) { + mappings.push({ + name: input.name, + type: 'datasource', + pluginId: input.pluginId, + value: selectedDs, + }); + } else { + unmappedDsInputs.push(input); + } + } + + return { + allMapped: unmappedDsInputs.length === 0, + mappings, + unmappedDsInputs, + }; +} + +/** + * Parses constant inputs from dashboard __inputs array. + * Constants need to be shown to the user for filling that information (the same as the import flow). + * + * @param allInputs - All inputs from dashboard.__inputs + * @returns Array of constant inputs with their default values + */ +export function parseConstantInputs(allInputs: Input[]): DashboardInput[] { + if (!allInputs || !Array.isArray(allInputs)) { + return []; + } + + return allInputs + .filter((input) => input.type === 'constant') + .map((input) => ({ + name: input.name, + label: input.label || input.name, + description: input.description, + info: input.description || 'Specify a string constant', + value: input.value || '', + type: InputType.Constant, + pluginId: undefined, + })); +} + +/** + * Converts constant inputs to InputMapping format for the interpolate API. + * Uses user-provided values or defaults from the dashboard. + * + * @param constantInputs - Array of constant inputs + * @param userValues - User-entered values (key: input name, value: user input) + * @returns Array of InputMapping for constants + */ +export function mapConstantInputs( + constantInputs: DashboardInput[], + userValues: Record +): InputMapping[] { + return constantInputs.map((input) => ({ + name: input.name, + type: 'constant', + value: userValues[input.name] !== undefined ? userValues[input.name] : input.value, + })); +} + +interface UserSelectedDatasourceMappings { + name: string; + pluginId: string; + datasource: { uid: string } | undefined; +} + +/** + * Maps user-selected datasources to InputMapping format. + * Used in the mapping form to convert user selections into the format required for dashboard interpolation. + * + * @param unmappedInputs - The datasource inputs that need mapping + * @param userSelectedDsMappings - Record of user selections keyed by input name + * @returns Array of InputMapping objects for user-selected datasources + */ +export function mapUserSelectedDatasources( + unmappedInputs: DataSourceInput[], + userSelectedDsMappings: Record +): InputMapping[] { + return unmappedInputs.map((input) => ({ + name: input.name, + type: 'datasource', + pluginId: input.pluginId, + value: userSelectedDsMappings[input.name]?.datasource?.uid || '', + })); +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/communityDashboardHelpers.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/communityDashboardHelpers.ts new file mode 100644 index 00000000000..fed08c7ae73 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/communityDashboardHelpers.ts @@ -0,0 +1,232 @@ +import { locationService } from '@grafana/runtime'; +import { DataSourceInput } from 'app/features/manage-dashboards/state/reducers'; + +import { DASHBOARD_LIBRARY_ROUTES } from '../../types'; +import { MappingContext } from '../SuggestedDashboardsModal'; +import { fetchCommunityDashboard, GnetDashboardDependency } from '../api/dashboardLibraryApi'; +import { CONTENT_KINDS, ContentKind, CREATION_ORIGINS, EventLocation, SOURCE_ENTRY_POINTS } from '../interactions'; +import { GnetDashboard, Link } from '../types'; + +import { InputMapping, tryAutoMapDatasources, parseConstantInputs, isDataSourceInput } from './autoMapDatasources'; + +/** + * Extract datasource types from URL parameters for tracking purposes. + * Supports two formats: + * - datasourceTypes: JSON array of datasource types (for community dashboards) + * - pluginId: Single datasource type (legacy format for provisioned dashboards) + * + * @returns Array of datasource type strings, or undefined if not available + */ +export function extractDatasourceTypesFromUrl(): string[] | undefined { + const params = locationService.getSearchObject(); + const datasourceTypesParam = params.datasourceTypes; + const pluginIdParam = params.pluginId; + + if (datasourceTypesParam && typeof datasourceTypesParam === 'string') { + try { + return JSON.parse(datasourceTypesParam); + } catch { + // If parsing fails, return undefined + return undefined; + } + } else if (pluginIdParam && typeof pluginIdParam === 'string') { + // Fallback to legacy pluginId for provisioned dashboards + return [pluginIdParam]; + } + + return undefined; +} + +/** + * Extract thumbnail URL from dashboard screenshots + */ +export function getThumbnailUrl(dashboard: GnetDashboard): string { + const thumbnail = dashboard.screenshots?.[0]?.links.find((l: Link) => l.rel === 'image')?.href ?? ''; + return thumbnail ? `/api/gnet${thumbnail}` : ''; +} + +/** + * Extract logo URL from dashboard logos + */ +export function getLogoUrl(dashboard: GnetDashboard): string { + const logo = dashboard.logos?.large || dashboard.logos?.small; + if (logo?.content && logo?.type) { + return `data:${logo.type};base64,${logo.content}`; + } + return ''; +} + +/** + * Format date string for display + */ +export function formatDate(dateString?: string): string { + if (!dateString) { + return 'N/A'; + } + const date = new Date(dateString); + return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); +} + +/** + * Create URL-friendly slug from dashboard name + */ +export function createSlug(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} + +/** + * Build Grafana.com URL for a dashboard + */ +export function buildGrafanaComUrl(dashboard: GnetDashboard): string { + return `https://grafana.com/grafana/dashboards/${dashboard.id}-${createSlug(dashboard.name)}/`; +} + +/** + * Build dashboard details object for display in card + */ +export interface DashboardDetails { + id: string; + datasource: string; + dependencies: string[]; + publishedBy: string; + lastUpdate: string; + grafanaComUrl: string; +} + +export function buildDashboardDetails(dashboard: GnetDashboard): DashboardDetails { + return { + id: String(dashboard.id), + datasource: dashboard.datasource || 'N/A', + dependencies: dashboard.datasource ? [dashboard.datasource] : [], + publishedBy: dashboard.orgName || dashboard.userName || 'Grafana Community', + lastUpdate: formatDate(dashboard.updatedAt || dashboard.publishedAt), + grafanaComUrl: buildGrafanaComUrl(dashboard), + }; +} + +/** + * Navigate to dashboard template route with mappings + */ +export function navigateToTemplate( + dashboardTitle: string, + gnetId: number, + datasourceUid: string, + mappings: InputMapping[], + eventLocation: EventLocation, + contentKind: ContentKind, + datasourceTypes?: string[] +): void { + const searchParams = new URLSearchParams({ + datasource: datasourceUid, + title: dashboardTitle, + gnetId: String(gnetId), + sourceEntryPoint: SOURCE_ENTRY_POINTS.DATASOURCE_PAGE, + creationOrigin: CREATION_ORIGINS.DASHBOARD_LIBRARY_COMMUNITY_DASHBOARD, + contentKind, + eventLocation, + mappings: JSON.stringify(mappings), + }); + + // Add datasource types for tracking if available + if (datasourceTypes && datasourceTypes.length > 0) { + searchParams.set('datasourceTypes', JSON.stringify(datasourceTypes)); + } + + locationService.push({ + pathname: DASHBOARD_LIBRARY_ROUTES.Template, + search: searchParams.toString(), + }); +} + +interface UseCommunityDashboardParams { + dashboard: GnetDashboard; + datasourceUid: string; + datasourceType: string; + eventLocation: 'empty_dashboard' | 'suggested_dashboards_modal_community_tab'; + onShowMapping?: (context: MappingContext) => void; +} + +/** + * Handles the flow when a user selects a community dashboard: + * 1. Tracks analytics + * 2. Fetches full dashboard JSON with __inputs + * 3. Attempts auto-mapping of datasources + * 4. Either navigates directly or shows mapping form + */ +export async function onUseCommunityDashboard({ + dashboard, + datasourceUid, + datasourceType, + eventLocation, + onShowMapping, +}: UseCommunityDashboardParams): Promise { + // Note: item_clicked tracking is done by the caller (CommunityDashboardSection or SuggestedDashboards) + // with the correct discoveryMethod before calling this function + try { + // Fetch full dashboard from Gcom, this is the JSON with __inputs + const fullDashboard = await fetchCommunityDashboard(dashboard.id); + const dashboardJson = fullDashboard.json; + + // Parse datasource requirements from __inputs + const dsInputs: DataSourceInput[] = dashboardJson.__inputs?.filter(isDataSourceInput) || []; + + // Extract datasource types for tracking purposes from dependencies + const datasourceTypes = + fullDashboard.dependencies?.items + ?.filter((dep: GnetDashboardDependency) => dep.pluginTypeCode === 'datasource') + .map((dep: GnetDashboardDependency) => dep.pluginSlug) + .filter(Boolean) || []; + + // Parse constant inputs - these always need user review + const constantInputs = parseConstantInputs(dashboardJson.__inputs || []); + + // Try auto-mapping datasources + const mappingResult = tryAutoMapDatasources(dsInputs, datasourceUid); + + // Decide whether to show mapping form or navigate directly + // Show mapping form if: (a) there are unmapped datasources OR (b) there are constants + const needsMapping = mappingResult.unmappedDsInputs.length > 0 || constantInputs.length > 0; + + if (!needsMapping) { + // No mapping needed - all datasources auto-mapped, no constants + navigateToTemplate( + dashboard.name, + dashboard.id, + datasourceUid, + mappingResult.mappings, + eventLocation, + CONTENT_KINDS.COMMUNITY_DASHBOARD, + datasourceTypes + ); + } else { + // Show mapping form for unmapped datasources and/or constants + if (onShowMapping) { + onShowMapping({ + dashboardName: dashboard.name, + dashboardJson, + unmappedDsInputs: mappingResult.unmappedDsInputs, + constantInputs, + existingMappings: mappingResult.mappings, + eventLocation, + contentKind: CONTENT_KINDS.COMMUNITY_DASHBOARD, + onInterpolateAndNavigate: (mappings) => + navigateToTemplate( + dashboard.name, + dashboard.id, + datasourceUid, + mappings, + eventLocation, + CONTENT_KINDS.COMMUNITY_DASHBOARD, + datasourceTypes + ), + }); + } + } + } catch (err) { + console.error('Error loading community dashboard:', err); + // TODO: Show error notification + } +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/provisionedDashboardHelpers.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/provisionedDashboardHelpers.ts new file mode 100644 index 00000000000..30a7c5c0059 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/utils/provisionedDashboardHelpers.ts @@ -0,0 +1,26 @@ +import dashboardLibrary1 from 'img/dashboard-library/dashboard_library_1.jpg'; +import dashboardLibrary2 from 'img/dashboard-library/dashboard_library_2.jpg'; +import dashboardLibrary3 from 'img/dashboard-library/dashboard_library_3.jpg'; +import dashboardLibrary4 from 'img/dashboard-library/dashboard_library_4.jpg'; +import dashboardLibrary5 from 'img/dashboard-library/dashboard_library_5.jpg'; +import dashboardLibrary6 from 'img/dashboard-library/dashboard_library_6.jpg'; + +/** + * Collection of placeholder images for provisioned/plugin dashboards + */ +export const DASHBOARD_PLACEHOLDER_IMAGES = [ + dashboardLibrary1, + dashboardLibrary2, + dashboardLibrary3, + dashboardLibrary4, + dashboardLibrary5, + dashboardLibrary6, +]; + +/** + * Get a placeholder image URL for a provisioned dashboard by index. + * Cycles through available images if index exceeds collection size. + */ +export function getProvisionedDashboardImageUrl(index: number): string { + return DASHBOARD_PLACEHOLDER_IMAGES[index % DASHBOARD_PLACEHOLDER_IMAGES.length]; +} diff --git a/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx b/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx index 5d1558fa7f5..645cc75f811 100644 --- a/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx +++ b/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx @@ -33,7 +33,6 @@ import { AdHocFilterItem, } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import config from 'app/core/config'; import { profiler } from 'app/core/profiler'; import { annotationServer } from 'app/features/annotations/api'; import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; @@ -121,7 +120,7 @@ export class PanelStateWrapper extends PureComponent { data: this.getInitialPanelDataState(), }; - if (config.featureToggles.panelMonitoring && this.getPanelContextApp() === CoreApp.PanelEditor) { + if (this.getPanelContextApp() === CoreApp.PanelEditor) { const panelInfo = { panelId: String(props.panel.id), panelType: props.panel.type, @@ -395,7 +394,7 @@ export class PanelStateWrapper extends PureComponent { } onPanelError = (error: Error) => { - if (config.featureToggles.panelMonitoring && this.getPanelContextApp() === CoreApp.PanelEditor) { + if (this.getPanelContextApp() === CoreApp.PanelEditor) { this.logPanelChangesOnError(); } @@ -543,7 +542,7 @@ export class PanelStateWrapper extends PureComponent { onChangeTimeRange={this.onChangeTimeRange} eventBus={dashboard.events} /> - {config.featureToggles.panelMonitoring && this.state.errorMessage === undefined && ( + {this.state.errorMessage === undefined && ( )} diff --git a/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts b/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts index 1872dea7f60..33d9a339e62 100644 --- a/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts +++ b/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts @@ -19,6 +19,7 @@ import { extractTargetVersionFromFilename, constructBackendOutputFilename, } from './__tests__/migrationTestUtils'; +import { getPanelPluginToMigrateTo } from './getPanelPluginToMigrateTo'; /* * Single Version Migration Test Design Explanation: @@ -117,6 +118,23 @@ describe('Backend / Frontend single version migration result comparison', () => // version in the backend is never added because it is returned from the backend as metadata delete frontendMigrationResult.version; + // since we are initializing panels inside collapsed rows with PanelModel in transformSceneToSaveModel (see createRowItemFromLegacyRow) + // and not in DashboardModel, this means that these panels will have automigratedFrom and panel type changed to the new panel type + // backend matches this behaviour by setting up autoMigrateFrom and type for nested panels too + // @ts-expect-error - we are using the type from the frontend migration result + for (const panel of frontendMigrationResult.panels) { + if (panel.type === 'row' && 'panels' in panel) { + for (const nestedPanel of panel.panels) { + const panelPluginToMigrateTo = getPanelPluginToMigrateTo(nestedPanel); + if (panelPluginToMigrateTo) { + // @ts-expect-error - we are using the type from the frontend migration result + nestedPanel.autoMigrateFrom = nestedPanel.type; + nestedPanel.type = panelPluginToMigrateTo; + } + } + } + } + expect(backendMigrationResult).toEqual(frontendMigrationResult); }); }); diff --git a/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts b/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts index f97715f088e..5666fa3710d 100644 --- a/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts +++ b/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts @@ -18,6 +18,7 @@ import { getJsonInputFiles, constructLatestVersionOutputFilename, } from './__tests__/migrationTestUtils'; +import { getPanelPluginToMigrateTo } from './getPanelPluginToMigrateTo'; /* * Backend / Frontend Migration Comparison Test Design Explanation: @@ -84,6 +85,23 @@ describe('Backend / Frontend result comparison', () => { // version in the backend is never added because it is returned from the backend as metadata delete frontendMigrationResult.version; + // since we are initializing panels inside collapsed rows with PanelModel in transformSceneToSaveModel (see createRowItemFromLegacyRow) + // and not in DashboardModel, this means that these panels will have automigratedFrom and panel type changed to the new panel type + // backend matches this behaviour by setting up autoMigrateFrom and type for nested panels too + // @ts-expect-error - we are using the type from the frontend migration result + for (const panel of frontendMigrationResult.panels) { + if (panel.type === 'row' && 'panels' in panel) { + for (const nestedPanel of panel.panels) { + const panelPluginToMigrateTo = getPanelPluginToMigrateTo(nestedPanel); + if (panelPluginToMigrateTo) { + // @ts-expect-error - we are using the type from the frontend migration result + nestedPanel.autoMigrateFrom = nestedPanel.type; + nestedPanel.type = panelPluginToMigrateTo; + } + } + } + } + expect(backendMigrationResult).toEqual(frontendMigrationResult); }); }); diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 774e73e37ad..81e2f878bde 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -15,8 +15,8 @@ import { HOME_DASHBOARD_CACHE_KEY, getDashboardScenePageStateManager, } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager'; +import { updateNavModel } from 'app/features/dashboard-scene/pages/utils'; import { buildNewDashboardSaveModel } from 'app/features/dashboard-scene/serialization/buildNewDashboardSaveModel'; -import { getFolderByUid } from 'app/features/folders/state/actions'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { toStateKey } from 'app/features/variables/utils'; @@ -96,11 +96,7 @@ async function fetchDashboard( // get parent folder (if it exists) and put it in the store // this will be used to populate the full breadcrumb trail if (dashDTO.meta.folderUid) { - try { - await dispatch(getFolderByUid(dashDTO.meta.folderUid)); - } catch (err) { - console.warn('Error fetching parent folder', dashDTO.meta.folderUid, 'for dashboard', err); - } + await updateNavModel(dashDTO.meta.folderUid); } if (args.fixUrl && dashDTO.meta.url && !playlistSrv.state.isPlaying) { @@ -124,7 +120,7 @@ async function fetchDashboard( // get parent folder (if it exists) and put it in the store // this will be used to populate the full breadcrumb trail if (args.urlFolderUid) { - await dispatch(getFolderByUid(args.urlFolderUid)); + await updateNavModel(args.urlFolderUid); } return await buildNewDashboardSaveModel(args.urlFolderUid); } diff --git a/public/app/features/dataframe-import/constants.ts b/public/app/features/dataframe-import/constants.ts deleted file mode 100644 index 9753ce71f08..00000000000 --- a/public/app/features/dataframe-import/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Accept } from 'react-dropzone'; - -export const acceptedFiles: Accept = { - 'text/plain': ['.csv', '.txt'], - 'application/json': ['.json'], -}; - -//This should probably set from grafana conf -export const maxFileSize = 500000; diff --git a/public/app/features/datasources/components/picker/DataSourceCard.tsx b/public/app/features/datasources/components/picker/DataSourceCard.tsx index dbdbdc2512a..4f30c07d894 100644 --- a/public/app/features/datasources/components/picker/DataSourceCard.tsx +++ b/public/app/features/datasources/components/picker/DataSourceCard.tsx @@ -41,6 +41,7 @@ export function DataSourceCard({ {description || ds.meta.name} {onToggleFavorite && !ds.meta.builtIn && ( { e.stopPropagation(); diff --git a/public/app/features/datasources/components/picker/DataSourceList.tsx b/public/app/features/datasources/components/picker/DataSourceList.tsx index 77e332dbc5f..691f6ae0d34 100644 --- a/public/app/features/datasources/components/picker/DataSourceList.tsx +++ b/public/app/features/datasources/components/picker/DataSourceList.tsx @@ -6,7 +6,7 @@ import { Observable } from 'rxjs'; import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceRef, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; -import { getTemplateSrv, reportInteraction, useFavoriteDatasources } from '@grafana/runtime'; +import { FavoriteDatasources, getTemplateSrv, reportInteraction } from '@grafana/runtime'; import { useStyles2, useTheme2 } from '@grafana/ui'; import { useDatasources, useKeyboardNavigatableList, useRecentlyUsedDataSources } from '../../hooks'; @@ -45,6 +45,7 @@ export interface DataSourceListProps { onClickEmptyStateCTA?: () => void; enableKeyboardNavigation?: boolean; dataSources?: Array>; + favoriteDataSources: FavoriteDatasources; } export function DataSourceList(props: DataSourceListProps) { @@ -58,7 +59,7 @@ export function DataSourceList(props: DataSourceListProps) { const theme = useTheme2(); const styles = getStyles(theme, selectedItemCssSelector); - const { className, current, onChange, enableKeyboardNavigation, onClickEmptyStateCTA } = props; + const { className, current, onChange, enableKeyboardNavigation, onClickEmptyStateCTA, favoriteDataSources } = props; const dataSources = useDatasources( { alerting: props.alerting, @@ -76,7 +77,6 @@ export function DataSourceList(props: DataSourceListProps) { ); const [recentlyUsedDataSources, pushRecentlyUsedDataSource] = useRecentlyUsedDataSources(); - const favoriteDataSources = useFavoriteDatasources(); const filteredDataSources = props.filter ? dataSources.filter(props.filter) : dataSources; diff --git a/public/app/features/datasources/components/picker/DataSourceModal.test.tsx b/public/app/features/datasources/components/picker/DataSourceModal.test.tsx index 39785614bdf..7ab78dda2ca 100644 --- a/public/app/features/datasources/components/picker/DataSourceModal.test.tsx +++ b/public/app/features/datasources/components/picker/DataSourceModal.test.tsx @@ -1,4 +1,4 @@ -import { queryByTestId, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { @@ -9,7 +9,6 @@ import { PluginType, locationUtil, } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { DataSourceModal, DataSourceModalProps } from './DataSourceModal'; @@ -106,44 +105,6 @@ describe('DataSourceDropdown', () => { }); }); - it('only displays the file drop area when the ff is enabled', async () => { - const defaultValue = config.featureToggles.editPanelCSVDragAndDrop; - config.featureToggles.editPanelCSVDragAndDrop = true; - setup({ uploadFile: true }); - - expect(await screen.queryByTestId('file-drop-zone-default-children')).toBeInTheDocument(); - config.featureToggles.editPanelCSVDragAndDrop = defaultValue; - }); - - it('does not show the file drop area when the ff is disabled', async () => { - const defaultValue = config.featureToggles.editPanelCSVDragAndDrop; - config.featureToggles.editPanelCSVDragAndDrop = false; - - setup({ uploadFile: true }); - expect(await screen.queryByTestId('file-drop-zone-default-children')).toBeNull(); - - config.featureToggles.editPanelCSVDragAndDrop = defaultValue; - }); - - it('should not display the drop zone by default', async () => { - const defaultValue = config.featureToggles.editPanelCSVDragAndDrop; - config.featureToggles.editPanelCSVDragAndDrop = true; - - const component = setup(); - - expect(queryByTestId(component.container, 'file-drop-zone-default-children')).toBeNull(); - config.featureToggles.editPanelCSVDragAndDrop = defaultValue; - }); - - it('should display the drop zone when uploadFile is enabled', async () => { - const defaultValue = config.featureToggles.editPanelCSVDragAndDrop; - config.featureToggles.editPanelCSVDragAndDrop = true; - setup({ uploadFile: true }); - - expect(await screen.queryByTestId('file-drop-zone-default-children')).toBeInTheDocument(); - config.featureToggles.editPanelCSVDragAndDrop = defaultValue; - }); - it('should fetch the DS applying the correct filters consistently across lists', async () => { const filters = { mixed: true, @@ -197,29 +158,6 @@ describe('DataSourceDropdown', () => { expect(await screen.findByText('No data sources found')).toBeInTheDocument(); }); - //Skipping this test as it's flaky on drone - it.skip('calls the onChange with the default query containing the file', async () => { - const user = userEvent.setup(); - config.featureToggles.editPanelCSVDragAndDrop = true; - const onChange = jest.fn(); - setup({ onChange, uploadFile: true }); - - const fileInput = ( - await screen.queryByTestId('file-drop-zone-default-children')! - ).parentElement!.parentElement!.querySelector('input'); - const file = new File([''], 'test.csv', { type: 'text/plain' }); - expect(fileInput).toBeInTheDocument(); - await user.upload(fileInput!, file); - const defaultQuery = onChange.mock.lastCall[1][0]; - expect(defaultQuery).toMatchObject({ - refId: 'A', - datasource: { type: 'grafana', uid: 'grafana' }, - queryType: 'snapshot', - file: { path: 'test.csv' }, - }); - config.featureToggles.editPanelCSVDragAndDrop = false; - }); - it('should call the onChange handler with the correct datasource', async () => { const user = userEvent.setup(); const onChange = jest.fn(); diff --git a/public/app/features/datasources/components/picker/DataSourceModal.tsx b/public/app/features/datasources/components/picker/DataSourceModal.tsx index ea6f7e63d80..af40a1596d9 100644 --- a/public/app/features/datasources/components/picker/DataSourceModal.tsx +++ b/public/app/features/datasources/components/picker/DataSourceModal.tsx @@ -4,22 +4,12 @@ import { useEffect, useMemo, useState } from 'react'; import { DataSourceInstanceSettings, DataSourceRef, GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { config, reportInteraction, useFavoriteDatasources } from '@grafana/runtime'; +import { reportInteraction, useFavoriteDatasources } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { - Modal, - FileDropzone, - FileDropzoneDefaultChildren, - useStyles2, - Input, - Icon, - ScrollContainer, -} from '@grafana/ui'; -import { acceptedFiles, maxFileSize } from 'app/features/dataframe-import/constants'; +import { Modal, useStyles2, Input, Icon, ScrollContainer } from '@grafana/ui'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; -import { getFileDropToQueryHandler } from 'app/plugins/datasource/grafana/utils'; -import { useDatasource, useDatasources } from '../../hooks'; +import { useDatasources } from '../../hooks'; import { AddNewDataSourceButton } from './AddNewDataSourceButton'; import { BuiltInDataSourceList } from './BuiltInDataSourceList'; @@ -29,7 +19,6 @@ import { matchDataSourceWithSearch } from './utils'; const INTERACTION_EVENT_NAME = 'dashboards_dspickermodal_clicked'; const INTERACTION_ITEM = { SELECT_DS: 'select_ds', - UPLOAD_FILE: 'upload_file', CONFIG_NEW_DS: 'config_new_ds', CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state', SEARCH: 'search', @@ -56,7 +45,6 @@ export interface DataSourceModalProps { alerting?: boolean; pluginId?: string; logs?: boolean; - uploadFile?: boolean; } export function DataSourceModal({ @@ -70,7 +58,6 @@ export function DataSourceModal({ alerting, pluginId, logs, - uploadFile, filter, onChange, current, @@ -96,8 +83,6 @@ export function DataSourceModal({ }); }; - const grafanaDS = useDatasource('-- Grafana --'); - // Get all datasources to report total_configured count const dataSources = useDatasources({ tracing, @@ -134,22 +119,6 @@ export function DataSourceModal({ [analyticsInteractionSrc] ); - const onFileDrop = getFileDropToQueryHandler((query, fileRejections) => { - if (!grafanaDS) { - return; - } - onChange(grafanaDS, [query]); - - reportInteraction(INTERACTION_EVENT_NAME, { - item: INTERACTION_ITEM.UPLOAD_FILE, - src: analyticsInteractionSrc, - }); - - if (fileRejections.length < 1) { - onDismiss(); - } - }); - // Built-in data sources used twice because of mobile layout adjustments // In movile the list is appended to the bottom of the DS list const BuiltInList = ({ className }: { className?: string }) => { @@ -219,6 +188,7 @@ export function DataSourceModal({ dashboard={dashboard} mixed={mixed} dataSources={dataSources} + favoriteDataSources={favoriteDataSources} /> @@ -230,20 +200,6 @@ export function DataSourceModal({
- {uploadFile && config.featureToggles.editPanelCSVDragAndDrop && ( - undefined} - options={{ - maxSize: maxFileSize, - multiple: false, - accept: acceptedFiles, - onDrop: onFileDrop, - }} - > - - - )}
diff --git a/public/app/features/datasources/components/picker/DataSourcePicker.test.tsx b/public/app/features/datasources/components/picker/DataSourcePicker.test.tsx index eeef14c9532..ec55592a480 100644 --- a/public/app/features/datasources/components/picker/DataSourcePicker.test.tsx +++ b/public/app/features/datasources/components/picker/DataSourcePicker.test.tsx @@ -11,8 +11,6 @@ import { } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { ModalRoot, ModalsProvider } from '@grafana/ui'; -import config from 'app/core/config'; -import { defaultFileUploadQuery } from 'app/plugins/datasource/grafana/types'; import { DataSourcePicker, DataSourcePickerProps } from './DataSourcePicker'; import * as utils from './utils'; @@ -290,18 +288,6 @@ describe('DataSourcePicker', () => { expect(screen.getByRole('link')).toHaveAttribute('href', '/my-sub-path/connections/datasources/new'); }); - it('should call onChange with the default query when add csv is clicked', async () => { - config.featureToggles.editPanelCSVDragAndDrop = true; - const onChange = jest.fn(); - await setupOpenDropdown(user, { onChange, uploadFile: true }); - - await user.click(await screen.findByText('Add csv or spreadsheet')); - - expect(onChange.mock.lastCall[1]).toEqual([defaultFileUploadQuery]); - expect(screen.queryByText('Open advanced data source picker')).toBeNull(); //Drop down is closed - config.featureToggles.editPanelCSVDragAndDrop = false; - }); - it('should open the modal when open advanced is clicked', async () => { const props = { onChange: jest.fn(), current: mockDS1.name }; render( diff --git a/public/app/features/datasources/components/picker/DataSourcePicker.tsx b/public/app/features/datasources/components/picker/DataSourcePicker.tsx index 7d2be3aa341..fdc8d245e31 100644 --- a/public/app/features/datasources/components/picker/DataSourcePicker.tsx +++ b/public/app/features/datasources/components/picker/DataSourcePicker.tsx @@ -11,12 +11,11 @@ import { Observable } from 'rxjs'; import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { reportInteraction, useFavoriteDatasources } from '@grafana/runtime'; +import { FavoriteDatasources, reportInteraction, useFavoriteDatasources } from '@grafana/runtime'; import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema'; import { Button, floatingUtils, Icon, Input, ModalsController, Portal, ScrollContainer, useStyles2 } from '@grafana/ui'; -import config from 'app/core/config'; import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection'; -import { defaultFileUploadQuery, GrafanaQuery } from 'app/plugins/datasource/grafana/types'; +import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { useDatasource, useDatasources } from '../../hooks'; @@ -30,7 +29,6 @@ export const INTERACTION_ITEM = { SEARCH: 'search', OPEN_DROPDOWN: 'open_dspicker', SELECT_DS: 'select_ds', - ADD_FILE: 'add_file', OPEN_ADVANCED_DS_PICKER: 'open_advanced_ds_picker', CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state', TOGGLE_FAVORITE: 'toggle_favorite', @@ -58,7 +56,6 @@ export interface DataSourcePickerProps { alerting?: boolean; pluginId?: string; logs?: boolean; - uploadFile?: boolean; filter?: (ds: DataSourceInstanceSettings) => boolean; } @@ -99,7 +96,6 @@ export function DataSourcePicker(props: DataSourcePickerProps) { // Used to move the focus to the footer when tabbing from the input const [footerRef, setFooterRef] = useState(); const currentDataSourceInstanceSettings = useDatasource(current); - const grafanaDS = useDatasource('-- Grafana --'); const currentValue = Boolean(!current && noDefault) ? undefined : currentDataSourceInstanceSettings; const prefixIcon = filterTerm && isOpen ? : ; @@ -178,14 +174,6 @@ export function DataSourcePicker(props: DataSourcePickerProps) { markerElement?.focus(); } - function onClickAddCSV() { - if (!grafanaDS) { - return; - } - - onChange(grafanaDS, [defaultFileUploadQuery]); - } - function onKeyDownInput(keyEvent: React.KeyboardEvent) { // From the input, it navigates to the footer if (keyEvent.key === 'Tab' && !keyEvent.shiftKey && isOpen) { @@ -302,10 +290,10 @@ export function DataSourcePicker(props: DataSourcePickerProps) { } }} onClose={onClose} - onClickAddCSV={onClickAddCSV} onDismiss={onClose} onNavigateOutsiteFooter={onNavigateOutsiteFooter} dataSources={dataSources} + favoriteDataSources={favoriteDataSources} />
@@ -334,7 +322,6 @@ function getStylesDropdown(theme: GrafanaTheme2, props: DataSourcePickerProps) { } export interface PickerContentProps extends DataSourcePickerProps { - onClickAddCSV?: () => void; keyboardEvents: Observable; style: React.CSSProperties; filterTerm?: string; @@ -343,10 +330,11 @@ export interface PickerContentProps extends DataSourcePickerProps { footerRef: (element: HTMLElement | null) => void; onNavigateOutsiteFooter: (e: React.KeyboardEvent) => void; dataSources: Array>; + favoriteDataSources: FavoriteDatasources; } const PickerContent = React.forwardRef((props, ref) => { - const { filterTerm, onChange, onClose, onClickAddCSV, current, filter, dataSources } = props; + const { filterTerm, onChange, current, filter, dataSources, favoriteDataSources } = props; const changeCallback = useCallback( (ds: DataSourceInstanceSettings) => { @@ -355,12 +343,6 @@ const PickerContent = React.forwardRef((prop [onChange] ); - const clickAddCSVCallback = useCallback(() => { - onClickAddCSV?.(); - onClose(); - reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.ADD_FILE }); - }, [onClickAddCSV, onClose]); - const styles = useStyles2(getStylesPickerContent); return ( @@ -368,6 +350,7 @@ const PickerContent = React.forwardRef((prop ((prop > -