diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index ec656436ee7..6b40b814064 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -440,6 +440,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
/e2e-playwright/dashboards/TestDashboard.json @grafana/dashboards-squad @grafana/grafana-search-navigate-organise
/e2e-playwright/dashboards/TestV2Dashboard.json @grafana/dashboards-squad
/e2e-playwright/dashboards/V2DashWithRepeats.json @grafana/dashboards-squad
+/e2e-playwright/dashboards/V2DashWithRowRepeats.json @grafana/dashboards-squad
/e2e-playwright/dashboards/V2DashWithTabRepeats.json @grafana/dashboards-squad
/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts @grafana/datapro
/e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @grafana/grafana-search-navigate-organise
@@ -542,6 +543,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-data/tsconfig.json @grafana/grafana-frontend-platform
/packages/grafana-data/test/ @grafana/grafana-frontend-platform
/packages/grafana-data/typings/ @grafana/grafana-frontend-platform
+/packages/grafana-data/scripts/ @grafana/grafana-frontend-platform
/packages/grafana-data/src/**/*logs* @grafana/observability-logs
/packages/grafana-data/src/context/plugins/ @grafana/plugins-platform-frontend
@@ -657,6 +659,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-runtime/src/services/LocationService.tsx @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/services/LocationSrv.ts @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/services/live.ts @grafana/dashboards-squad
+/packages/grafana-runtime/src/services/pluginMeta @grafana/plugins-platform-frontend
/packages/grafana-runtime/src/utils/chromeHeaderHeight.ts @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/utils/DataSourceWithBackend* @grafana/grafana-datasources-core-services
/packages/grafana-runtime/src/utils/licensing.ts @grafana/grafana-operator-experience-squad
@@ -1275,6 +1278,7 @@ embed.go @grafana/grafana-as-code
/.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform
/.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform
/.github/workflows/i18n-verify.yml @grafana/grafana-frontend-platform
+/.github/workflows/deploy-storybook.yml @grafana/grafana-frontend-platform
/.github/workflows/deploy-storybook-preview.yml @grafana/grafana-frontend-platform
/.github/workflows/scripts/crowdin/create-tasks.ts @grafana/grafana-frontend-platform
/.github/workflows/scripts/publish-frontend-metrics.mts @grafana/grafana-frontend-platform
diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml
index 2b8484d7cf6..1fd10ae5540 100644
--- a/.github/actions/change-detection/action.yml
+++ b/.github/actions/change-detection/action.yml
@@ -14,6 +14,9 @@ outputs:
frontend:
description: Whether the frontend or self has changed in any way
value: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}
+ frontend-packages:
+ description: Whether any frontend packages have changed
+ value: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}
e2e:
description: Whether the e2e tests or self have changed in any way
value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' ||
@@ -97,6 +100,12 @@ runs:
- '.yarn/**'
- 'apps/dashboard/pkg/migration/**'
- '${{ inputs.self }}'
+ frontend_packages:
+ - '.github/actions/checkout/**'
+ - '.github/actions/change-detection/**'
+ - 'packages/**'
+ - './scripts/validate-npm-packages.sh'
+ - '${{ inputs.self }}'
e2e:
- 'e2e/**'
- 'e2e-playwright/**'
@@ -153,6 +162,8 @@ runs:
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
echo "Frontend: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}"
+ echo "Frontend packages: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}"
+ echo " --> ${{ steps.changed-files.outputs.frontend_packages_all_changed_files }}"
echo "E2E: ${{ steps.changed-files.outputs.e2e_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}"
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml
index 5762389f83b..92ffd43593c 100644
--- a/.github/actions/setup-node/action.yml
+++ b/.github/actions/setup-node/action.yml
@@ -4,8 +4,8 @@ description: Sets up a node.js environment with presets for the Grafana reposito
runs:
using: "composite"
steps:
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
\ No newline at end of file
+ cache-dependency-path: 'yarn.lock'
diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml
new file mode 100644
index 00000000000..08bffaeb891
--- /dev/null
+++ b/.github/workflows/deploy-storybook.yml
@@ -0,0 +1,79 @@
+name: Deploy Storybook
+
+on:
+ workflow_dispatch:
+ # push:
+ # branches:
+ # - main
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions: {}
+
+jobs:
+ detect-changes:
+ # Only run in grafana/grafana
+ if: github.repository == 'grafana/grafana'
+ name: Detect whether code changed
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ outputs:
+ changed-frontend-packages: ${{ steps.detect-changes.outputs.frontend-packages }}
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ persist-credentials: true # required to get more history in the changed-files action
+ fetch-depth: 2
+ - name: Detect changes
+ id: detect-changes
+ uses: ./.github/actions/change-detection
+ with:
+ self: .github/workflows/deploy-storybook.yml
+ deploy-storybook:
+ name: Deploy Storybook
+ runs-on: ubuntu-latest
+ needs: detect-changes
+ # Only run in grafana/grafana
+ if: github.repository == 'grafana/grafana' && needs.detect-changes.outputs.changed-frontend-packages == 'true'
+ permissions:
+ contents: read
+ id-token: write
+
+ env:
+ BUCKET_NAME: grafana-storybook
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+ with:
+ persist-credentials: false
+
+ - name: Setup Node.js
+ uses: ./.github/actions/setup-node
+
+ - name: Install dependencies
+ run: yarn install --immutable
+
+ - name: Build storybook
+ run: yarn storybook:build
+
+ # Create the GCS folder name
+ # Right now, this just returns "canary"
+ # But we'll expand this to work for "latest" as well in the future
+ - name: Create deploy name
+ id: create-deploy-name
+ run: |
+ echo "deploy-name=canary" >> "$GITHUB_OUTPUT"
+
+ - name: Upload Storybook
+ uses: grafana/shared-workflows/actions/push-to-gcs@main
+ with:
+ environment: prod
+ bucket: ${{ env.BUCKET_NAME }}
+ bucket_path: ${{ steps.create-deploy-name.outputs.deploy-name }}
+ path: packages/grafana-ui/dist/storybook
+ service_account: github-gf-storybook-deploy@grafanalabs-workload-identity.iam.gserviceaccount.com
+ parent: false
diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml
index 02833d61149..539d57b1742 100644
--- a/.github/workflows/frontend-lint.yml
+++ b/.github/workflows/frontend-lint.yml
@@ -17,6 +17,7 @@ jobs:
outputs:
changed: ${{ steps.detect-changes.outputs.frontend }}
prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }}
+ changed-frontend-packages: ${{ steps.detect-changes.outputs.frontend-packages }}
steps:
- uses: actions/checkout@v5
with:
@@ -42,11 +43,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- - uses: actions/setup-node@v6
- with:
- node-version-file: '.nvmrc'
- cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
- run: yarn install --immutable --check-cache
- run: yarn run prettier:check
- run: yarn run lint
@@ -63,11 +61,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- - uses: actions/setup-node@v6
- with:
- node-version-file: '.nvmrc'
- cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
@@ -89,11 +84,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- - uses: actions/setup-node@v6
- with:
- node-version-file: '.nvmrc'
- cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
- run: yarn install --immutable --check-cache
- run: yarn run typecheck
lint-frontend-typecheck-enterprise:
@@ -109,11 +101,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- - uses: actions/setup-node@v6
- with:
- node-version-file: '.nvmrc'
- cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
@@ -133,11 +122,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- - uses: actions/setup-node@v6
- with:
- node-version-file: '.nvmrc'
- cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
- run: yarn install --immutable --check-cache
- name: Generate API clients
run: |
@@ -164,11 +150,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- - uses: actions/setup-node@v6
- with:
- node-version-file: '.nvmrc'
- cache: 'yarn'
- cache-dependency-path: 'yarn.lock'
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
@@ -187,3 +170,26 @@ jobs:
echo "${uncommited_error_message}"
exit 1
fi
+ lint-frontend-packed-packages:
+ needs: detect-changes
+ permissions:
+ contents: read
+ id-token: write
+ if: github.event_name == 'pull_request' && needs.detect-changes.outputs.changed-frontend-packages == 'true'
+ name: Verify packed frontend packages
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout build commit
+ uses: actions/checkout@v5
+ with:
+ persist-credentials: false
+ - name: Setup Node
+ uses: ./.github/actions/setup-node
+ - name: Install dependencies
+ run: yarn install --immutable
+ - name: Build and pack packages
+ run: |
+ yarn run packages:build
+ yarn run packages:pack
+ - name: Validate packages
+ run: ./scripts/validate-npm-packages.sh
diff --git a/.golangci.yml b/.golangci.yml
index 069e88632ff..d7037bf6fac 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -121,6 +121,8 @@ linters:
- '**/pkg/tsdb/zipkin/**/*'
- '**/pkg/tsdb/jaeger/*'
- '**/pkg/tsdb/jaeger/**/*'
+ - '**/pkg/tsdb/elasticsearch/*'
+ - '**/pkg/tsdb/elasticsearch/**/*'
deny:
- pkg: github.com/grafana/grafana/pkg/api
desc: Core plugins are not allowed to depend on Grafana core packages
diff --git a/Makefile b/Makefile
index a5353e95567..e4a261acb4b 100644
--- a/Makefile
+++ b/Makefile
@@ -135,7 +135,7 @@ i18n-extract-enterprise:
@echo "Skipping i18n extract for Enterprise: not enabled"
else
i18n-extract-enterprise:
- @echo "Extracting i18n strings for Enterprise"
+ @echo "Extracting i18n strings for Enterprise"
cd public/locales/enterprise && yarn run i18next-cli extract --sync-primary
endif
@@ -227,6 +227,10 @@ fix-cue:
gen-jsonnet:
go generate ./devenv/jsonnet
+.PHONY: gen-themes
+gen-themes:
+ go generate ./pkg/services/preference
+
.PHONY: update-workspace
update-workspace: gen-go
@echo "updating workspace"
@@ -244,6 +248,7 @@ build-go-fast: ## Build all Go binaries without updating workspace.
.PHONY: build-backend
build-backend: ## Build Grafana backend.
@echo "build backend"
+ $(MAKE) gen-themes
$(GO) run build.go $(GO_BUILD_FLAGS) build-backend
.PHONY: build-air
diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go
index cae29e181fd..c35c47e45f6 100644
--- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go
+++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go
@@ -28,7 +28,7 @@ type check struct {
PluginStore pluginstore.Store
PluginContextProvider PluginContextProvider
PluginClient plugins.Client
- PluginRepo repo.Service
+ PluginRepo checks.PluginInfoGetter
GrafanaVersion string
pluginCanBeInstalledCache map[string]bool
pluginExistsCacheMu sync.RWMutex
@@ -39,7 +39,7 @@ func New(
pluginStore pluginstore.Store,
pluginContextProvider PluginContextProvider,
pluginClient plugins.Client,
- pluginRepo repo.Service,
+ pluginRepo checks.PluginInfoGetter,
grafanaVersion string,
) checks.Check {
return &check{
diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/missing_plugin_step.go b/apps/advisor/pkg/app/checks/datasourcecheck/missing_plugin_step.go
index 1d784f0a544..9b70f5d0896 100644
--- a/apps/advisor/pkg/app/checks/datasourcecheck/missing_plugin_step.go
+++ b/apps/advisor/pkg/app/checks/datasourcecheck/missing_plugin_step.go
@@ -15,7 +15,7 @@ import (
type missingPluginStep struct {
PluginStore pluginstore.Store
- PluginRepo repo.Service
+ PluginRepo checks.PluginInfoGetter
GrafanaVersion string
}
diff --git a/apps/advisor/pkg/app/checks/ifaces.go b/apps/advisor/pkg/app/checks/ifaces.go
index 6573253b557..2b205933151 100644
--- a/apps/advisor/pkg/app/checks/ifaces.go
+++ b/apps/advisor/pkg/app/checks/ifaces.go
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
+ "github.com/grafana/grafana/pkg/plugins/repo"
)
// Check returns metadata about the check being executed and the list of Steps
@@ -37,3 +38,10 @@ type Step interface {
// Run executes the step for an item and returns a report
Run(ctx context.Context, log logging.Logger, obj *advisorv0alpha1.CheckSpec, item any) ([]advisorv0alpha1.CheckReportFailure, error)
}
+
+// PluginInfoGetter is a minimal interface for retrieving plugin information from a repository.
+// It contains only the GetPluginsInfo method used by plugincheck and datasourcecheck.
+type PluginInfoGetter interface {
+ // GetPluginsInfo will return a list of plugins from grafana.com/api/plugins.
+ GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error)
+}
diff --git a/apps/advisor/pkg/app/checks/plugincheck/check.go b/apps/advisor/pkg/app/checks/plugincheck/check.go
index 3d261f81b67..00bc293e86c 100644
--- a/apps/advisor/pkg/app/checks/plugincheck/check.go
+++ b/apps/advisor/pkg/app/checks/plugincheck/check.go
@@ -17,7 +17,7 @@ const (
func New(
pluginStore pluginstore.Store,
- pluginRepo repo.Service,
+ pluginRepo checks.PluginInfoGetter,
updateChecker pluginchecker.PluginUpdateChecker,
pluginErrorResolver plugins.ErrorResolver,
grafanaVersion string,
@@ -33,7 +33,7 @@ func New(
type check struct {
PluginStore pluginstore.Store
- PluginRepo repo.Service
+ PluginRepo checks.PluginInfoGetter
updateChecker pluginchecker.PluginUpdateChecker
pluginErrorResolver plugins.ErrorResolver
GrafanaVersion string
diff --git a/apps/alerting/historian/go.mod b/apps/alerting/historian/go.mod
index a79829d45c2..9bce171ed16 100644
--- a/apps/alerting/historian/go.mod
+++ b/apps/alerting/historian/go.mod
@@ -4,7 +4,7 @@ go 1.25.5
require (
github.com/go-kit/log v0.2.1
- github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f
+ github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
github.com/grafana/grafana-app-sdk v0.48.7
github.com/grafana/grafana-app-sdk/logging v0.48.7
diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum
index d45d418dfb8..73b2a5c991b 100644
--- a/apps/alerting/historian/go.sum
+++ b/apps/alerting/historian/go.sum
@@ -243,8 +243,8 @@ 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
-github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
+github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f h1:3bXOyht68qkfvD6Y8z8XoenFbytSSOIkr/s+AqRzj0o=
+github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f/go.mod h1:Ji0SfJChcwjgq8ljy6Y5CcYfHfAYKXjKYeysOoDS/6s=
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4=
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue
index 6488de41c96..55094fe3447 100644
--- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue
+++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue
@@ -254,8 +254,18 @@ FieldConfig: {
// custom is specified by the FieldConfig field
// in panel plugin schemas.
custom?: {...}
+
+ // Calculate min max per field
+ fieldMinMax?: bool
+
+ // How null values should be handled when calculating field stats
+ // "null" - Include null values, "connected" - Ignore nulls, "null as zero" - Treat nulls as zero
+ nullValueMode?: NullValueMode
}
+// How null values should be handled
+NullValueMode: "null" | "connected" | "null as zero"
+
DynamicConfigValue: {
id: string | *""
value?: _
diff --git a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue
index a8e1f121213..0802430907e 100644
--- a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue
+++ b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue
@@ -250,8 +250,18 @@ FieldConfig: {
// custom is specified by the FieldConfig field
// in panel plugin schemas.
custom?: {...}
+
+ // Calculate min max per field
+ fieldMinMax?: bool
+
+ // How null values should be handled when calculating field stats
+ // "null" - Include null values, "connected" - Ignore nulls, "null as zero" - Treat nulls as zero
+ nullValueMode?: NullValueMode
}
+// How null values should be handled
+NullValueMode: "null" | "connected" | "null as zero"
+
DynamicConfigValue: {
id: string | *""
value?: _
diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue
index 2b027ff98e1..293082ab82f 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue
+++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue
@@ -258,8 +258,18 @@ FieldConfig: {
// custom is specified by the FieldConfig field
// in panel plugin schemas.
custom?: {...}
+
+ // Calculate min max per field
+ fieldMinMax?: bool
+
+ // How null values should be handled when calculating field stats
+ // "null" - Include null values, "connected" - Ignore nulls, "null as zero" - Treat nulls as zero
+ nullValueMode?: NullValueMode
}
+// How null values should be handled
+NullValueMode: "null" | "connected" | "null as zero"
+
DynamicConfigValue: {
id: string | *""
value?: _
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 3f594306ef5..f7ccfdd4925 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go
+++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go
@@ -419,6 +419,11 @@ type DashboardFieldConfig struct {
// custom is specified by the FieldConfig field
// in panel plugin schemas.
Custom map[string]interface{} `json:"custom,omitempty"`
+ // Calculate min max per field
+ FieldMinMax *bool `json:"fieldMinMax,omitempty"`
+ // How null values should be handled when calculating field stats
+ // "null" - Include null values, "connected" - Ignore nulls, "null as zero" - Treat nulls as zero
+ NullValueMode *DashboardNullValueMode `json:"nullValueMode,omitempty"`
}
// NewDashboardFieldConfig creates a new DashboardFieldConfig object.
@@ -745,6 +750,16 @@ func NewDashboardActionVariable() *DashboardActionVariable {
// +k8s:openapi-gen=true
const DashboardActionVariableType = "string"
+// How null values should be handled
+// +k8s:openapi-gen=true
+type DashboardNullValueMode string
+
+const (
+ DashboardNullValueModeNull DashboardNullValueMode = "null"
+ DashboardNullValueModeConnected DashboardNullValueMode = "connected"
+ DashboardNullValueModeNullAsZero DashboardNullValueMode = "null as zero"
+)
+
// +k8s:openapi-gen=true
type DashboardDynamicConfigValue struct {
Id string `json:"id"`
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 4c6f3f5ed20..926d50cb49d 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go
+++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go
@@ -2277,6 +2277,20 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardFieldConfig(ref common.Referenc
},
},
},
+ "fieldMinMax": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Calculate min max per field",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "nullValueMode": {
+ SchemaProps: spec.SchemaProps{
+ Description: "How null values should be handled when calculating field stats \"null\" - Include null values, \"connected\" - Ignore nulls, \"null as zero\" - Treat nulls as zero",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
},
},
diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue
index 375ba67f003..41ab7bc3fa7 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue
@@ -254,8 +254,18 @@ FieldConfig: {
// custom is specified by the FieldConfig field
// in panel plugin schemas.
custom?: {...}
+
+ // Calculate min max per field
+ fieldMinMax?: bool
+
+ // How null values should be handled when calculating field stats
+ // "null" - Include null values, "connected" - Ignore nulls, "null as zero" - Treat nulls as zero
+ nullValueMode?: NullValueMode
}
+// How null values should be handled
+NullValueMode: "null" | "connected" | "null as zero"
+
DynamicConfigValue: {
id: string | *""
value?: _
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 96054cb2fc4..06f1e1df599 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go
@@ -423,6 +423,11 @@ type DashboardFieldConfig struct {
// custom is specified by the FieldConfig field
// in panel plugin schemas.
Custom map[string]interface{} `json:"custom,omitempty"`
+ // Calculate min max per field
+ FieldMinMax *bool `json:"fieldMinMax,omitempty"`
+ // How null values should be handled when calculating field stats
+ // "null" - Include null values, "connected" - Ignore nulls, "null as zero" - Treat nulls as zero
+ NullValueMode *DashboardNullValueMode `json:"nullValueMode,omitempty"`
}
// NewDashboardFieldConfig creates a new DashboardFieldConfig object.
@@ -749,6 +754,16 @@ func NewDashboardActionVariable() *DashboardActionVariable {
// +k8s:openapi-gen=true
const DashboardActionVariableType = "string"
+// How null values should be handled
+// +k8s:openapi-gen=true
+type DashboardNullValueMode string
+
+const (
+ DashboardNullValueModeNull DashboardNullValueMode = "null"
+ DashboardNullValueModeConnected DashboardNullValueMode = "connected"
+ DashboardNullValueModeNullAsZero DashboardNullValueMode = "null as zero"
+)
+
// +k8s:openapi-gen=true
type DashboardDynamicConfigValue struct {
Id string `json:"id"`
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 402810f6e53..73c4d1f7349 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go
@@ -2284,6 +2284,20 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardFieldConfig(ref common.Reference
},
},
},
+ "fieldMinMax": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Calculate min max per field",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "nullValueMode": {
+ SchemaProps: spec.SchemaProps{
+ Description: "How null values should be handled when calculating field stats \"null\" - Include null values, \"connected\" - Ignore nulls, \"null as zero\" - Treat nulls as zero",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
},
},
diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go
index e94d66fec82..aee89730dc1 100644
--- a/apps/dashboard/pkg/apis/dashboard_manifest.go
+++ b/apps/dashboard/pkg/apis/dashboard_manifest.go
@@ -32,10 +32,10 @@ var (
rawSchemaDashboardv1beta1 = []byte(`{"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`)
versionSchemaDashboardv1beta1 app.VersionSchema
_ = json.Unmarshal(rawSchemaDashboardv1beta1, &versionSchemaDashboardv1beta1)
- rawSchemaDashboardv2alpha1 = []byte(`{"Action":{"additionalProperties":false,"properties":{"confirmation":{"type":"string"},"fetch":{"$ref":"#/components/schemas/FetchOptions"},"infinity":{"$ref":"#/components/schemas/InfinityOptions"},"oneClick":{"type":"boolean"},"style":{"additionalProperties":false,"properties":{"backgroundColor":{"type":"string"}},"type":"object"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionType"},"variables":{"items":{"$ref":"#/components/schemas/ActionVariable"},"type":"array"}},"required":["type","title"],"type":"object"},"ActionType":{"enum":["fetch","infinity"],"type":"string"},"ActionVariable":{"additionalProperties":false,"properties":{"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionVariableType"}},"required":["key","name","type"],"type":"object"},"ActionVariableType":{"const":"string","description":"Action variable type","type":"string"},"AdHocFilterWithLabels":{"additionalProperties":false,"description":"Define the AdHocFilterWithLabels type","properties":{"condition":{"description":"@deprecated","type":"string"},"forceEdit":{"type":"boolean"},"key":{"type":"string"},"keyLabel":{"type":"string"},"operator":{"type":"string"},"origin":{"$ref":"#/components/schemas/FilterOrigin"},"value":{"type":"string"},"valueLabels":{"items":{"type":"string"},"type":"array"},"values":{"items":{"type":"string"},"type":"array"}},"required":["key","operator","value"],"type":"object"},"AdhocVariableKind":{"additionalProperties":false,"description":"Adhoc variable kind","properties":{"kind":{"const":"AdhocVariable","type":"string"},"spec":{"$ref":"#/components/schemas/AdhocVariableSpec"}},"required":["kind","spec"],"type":"object"},"AdhocVariableSpec":{"additionalProperties":false,"description":"Adhoc variable specification","properties":{"allowCustomValue":{"default":true,"type":"boolean"},"baseFilters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"defaultKeys":{"items":{"$ref":"#/components/schemas/MetricFindValue"},"type":"array"},"description":{"type":"string"},"filters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","baseFilters","filters","defaultKeys","hide","skipUrlSync","allowCustomValue"],"type":"object"},"AnnotationEventFieldMapping":{"additionalProperties":false,"description":"Annotation event field mapping. Defines how to map a data frame field to an annotation event field.","properties":{"regex":{"description":"Regular expression to apply to the field value","type":"string"},"source":{"default":"field","description":"Source type for the field value","type":"string"},"value":{"description":"Constant value to use when source is \"text\"","type":"string"}},"type":"object"},"AnnotationPanelFilter":{"additionalProperties":false,"properties":{"exclude":{"default":false,"description":"Should the specified panels be included or excluded","type":"boolean"},"ids":{"description":"Panel IDs that should be included or excluded","items":{"type":"integer"},"type":"array"}},"required":["ids"],"type":"object"},"AnnotationQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"AnnotationQuery","type":"string"},"spec":{"$ref":"#/components/schemas/AnnotationQuerySpec"}},"required":["kind","spec"],"type":"object"},"AnnotationQuerySpec":{"additionalProperties":false,"properties":{"builtIn":{"default":false,"type":"boolean"},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"enable":{"type":"boolean"},"filter":{"$ref":"#/components/schemas/AnnotationPanelFilter"},"hide":{"type":"boolean"},"iconColor":{"type":"string"},"legacyOptions":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"Catch-all field for datasource-specific properties","type":"object"},"mappings":{"additionalProperties":{"$ref":"#/components/schemas/AnnotationEventFieldMapping"},"description":"Mappings define how to convert data frame fields to annotation event fields.","type":"object"},"name":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"}},"required":["enable","hide","iconColor","name"],"type":"object"},"AutoGridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutItemSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"element":{"$ref":"#/components/schemas/ElementReference"},"repeat":{"$ref":"#/components/schemas/AutoGridRepeatOptions"}},"required":["element"],"type":"object"},"AutoGridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutSpec":{"additionalProperties":false,"properties":{"columnWidth":{"type":"number"},"columnWidthMode":{"default":"standard","enum":["narrow","standard","wide","custom"],"type":"string"},"fillScreen":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/AutoGridLayoutItemKind"},"type":"array"},"maxColumnCount":{"default":3,"type":"number"},"rowHeight":{"type":"number"},"rowHeightMode":{"default":"standard","enum":["short","standard","tall","custom"],"type":"string"}},"required":["columnWidthMode","rowHeightMode","items"],"type":"object"},"AutoGridRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"ConditionalRenderingDataKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingData","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingDataSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingDataSpec":{"additionalProperties":false,"properties":{"value":{"type":"boolean"}},"required":["value"],"type":"object"},"ConditionalRenderingGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingGroup","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingGroupSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingGroupSpec":{"additionalProperties":false,"properties":{"condition":{"enum":["and","or"],"type":"string"},"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ConditionalRenderingVariableKind"},{"$ref":"#/components/schemas/ConditionalRenderingDataKind"},{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeKind"}]},"type":"array"},"visibility":{"enum":["show","hide"],"type":"string"}},"required":["visibility","condition","items"],"type":"object"},"ConditionalRenderingTimeRangeSizeKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingTimeRangeSize","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingTimeRangeSizeSpec":{"additionalProperties":false,"properties":{"value":{"type":"string"}},"required":["value"],"type":"object"},"ConditionalRenderingVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingVariableSpec":{"additionalProperties":false,"properties":{"operator":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"value":{"type":"string"},"variable":{"type":"string"}},"required":["variable","operator","value"],"type":"object"},"ConstantVariableKind":{"additionalProperties":false,"description":"Constant variable kind","properties":{"kind":{"const":"ConstantVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConstantVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConstantVariableSpec":{"additionalProperties":false,"description":"Constant variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","hide","skipUrlSync"],"type":"object"},"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"CustomVariableKind":{"additionalProperties":false,"description":"Custom variable kind","properties":{"kind":{"const":"CustomVariable","type":"string"},"spec":{"$ref":"#/components/schemas/CustomVariableSpec"}},"required":["kind","spec"],"type":"object"},"CustomVariableSpec":{"additionalProperties":false,"description":"Custom variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"},"valuesFormat":{"enum":["csv","json"],"type":"string"}},"required":["name","query","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"DashboardCursorSync":{"description":"\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.","enum":["Crosshair","Tooltip","Off"],"type":"string"},"DashboardLink":{"additionalProperties":false,"description":"Links with references to other dashboards or external resources","properties":{"asDropdown":{"default":false,"description":"If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards","type":"boolean"},"icon":{"description":"Icon name to be displayed with the link","type":"string"},"includeVars":{"default":false,"description":"If true, includes current template variables values in the link as query params","type":"boolean"},"keepTime":{"default":false,"description":"If true, includes current time range in the link as query params","type":"boolean"},"placement":{"$ref":"#/components/schemas/DashboardLinkPlacement","description":"Placement can be used to display the link somewhere else on the dashboard other than above the visualisations."},"tags":{"description":"List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards","items":{"type":"string"},"type":"array"},"targetBlank":{"default":false,"description":"If true, the link will be opened in a new tab","type":"boolean"},"title":{"description":"Title to display with the link","type":"string"},"tooltip":{"description":"Tooltip to display when the user hovers their mouse over it","type":"string"},"type":{"$ref":"#/components/schemas/DashboardLinkType","description":"Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)\nFIXME: The type is generated as ` + "`" + `type: DashboardLinkType | dashboardLinkType.Link;` + "`" + ` but it should be ` + "`" + `type: DashboardLinkType` + "`" + `"},"url":{"description":"Link URL. Only required/valid if the type is link","type":"string"}},"required":["title","type","icon","tooltip","tags","asDropdown","targetBlank","includeVars","keepTime"],"type":"object"},"DashboardLinkPlacement":{"const":"inControlsMenu","description":"Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu","type":"string"},"DashboardLinkType":{"description":"Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)","enum":["link","dashboards"],"type":"string"},"DataLink":{"additionalProperties":false,"properties":{"targetBlank":{"type":"boolean"},"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"type":"object"},"DataQueryKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a DataQueryKind is the datasource type","type":"string"},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"}},"required":["kind","spec"],"type":"object"},"DataSourceRef":{"additionalProperties":false,"properties":{"type":{"description":"The plugin type-id","type":"string"},"uid":{"description":"Specific datasource instance","type":"string"}},"type":"object"},"DataTopic":{"description":"A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.","enum":["series","annotations","alertStates"],"type":"string"},"DataTransformerConfig":{"additionalProperties":false,"description":"Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.","properties":{"disabled":{"description":"Disabled transformations are skipped","type":"boolean"},"filter":{"$ref":"#/components/schemas/MatcherConfig","description":"Optional frame matcher. When missing it will be applied to all results"},"id":{"description":"Unique identifier of transformer","type":"string"},"options":{"additionalProperties":{},"description":"Options to be passed to the transformer\nValid options depend on the transformer id","type":"object"},"topic":{"$ref":"#/components/schemas/DataTopic","description":"Where to pull DataFrames from as input to transformation"}},"required":["id","options"],"type":"object"},"DatasourceVariableKind":{"additionalProperties":false,"description":"Datasource variable kind","properties":{"kind":{"const":"DatasourceVariable","type":"string"},"spec":{"$ref":"#/components/schemas/DatasourceVariableSpec"}},"required":["kind","spec"],"type":"object"},"DatasourceVariableSpec":{"additionalProperties":false,"description":"Datasource variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"pluginId":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","pluginId","refresh","regex","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"DynamicConfigValue":{"additionalProperties":false,"properties":{"id":{"default":"","type":"string"},"value":{"additionalProperties":{},"type":"object"}},"required":["id"],"type":"object"},"Element":{"description":"Supported dashboard elements\n|* more element types in the future","oneOf":[{"$ref":"#/components/schemas/PanelKind"},{"$ref":"#/components/schemas/LibraryPanelKind"}]},"ElementReference":{"additionalProperties":false,"properties":{"kind":{"const":"ElementReference","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},"FetchOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url"],"type":"object"},"FieldColor":{"additionalProperties":false,"description":"Map a field to a color.","properties":{"fixedColor":{"description":"The fixed color value for fixed or shades color modes.","type":"string"},"mode":{"$ref":"#/components/schemas/FieldColorModeId","description":"The main color scheme mode."},"seriesBy":{"$ref":"#/components/schemas/FieldColorSeriesByMode","description":"Some visualizations need to know how to assign a series color from by value color schemes."}},"required":["mode"],"type":"object"},"FieldColorModeId":{"description":"Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n` + "`" + `thresholds` + "`" + `: From thresholds. Informs Grafana to take the color from the matching threshold\n` + "`" + `palette-classic` + "`" + `: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `palette-classic-by-name` + "`" + `: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `continuous-viridis` + "`" + `: Continuous Viridis palette mode\n` + "`" + `continuous-magma` + "`" + `: Continuous Magma palette mode\n` + "`" + `continuous-plasma` + "`" + `: Continuous Plasma palette mode\n` + "`" + `continuous-inferno` + "`" + `: Continuous Inferno palette mode\n` + "`" + `continuous-cividis` + "`" + `: Continuous Cividis palette mode\n` + "`" + `continuous-GrYlRd` + "`" + `: Continuous Green-Yellow-Red palette mode\n` + "`" + `continuous-RdYlGr` + "`" + `: Continuous Red-Yellow-Green palette mode\n` + "`" + `continuous-BlYlRd` + "`" + `: Continuous Blue-Yellow-Red palette mode\n` + "`" + `continuous-YlRd` + "`" + `: Continuous Yellow-Red palette mode\n` + "`" + `continuous-BlPu` + "`" + `: Continuous Blue-Purple palette mode\n` + "`" + `continuous-YlBl` + "`" + `: Continuous Yellow-Blue palette mode\n` + "`" + `continuous-blues` + "`" + `: Continuous Blue palette mode\n` + "`" + `continuous-reds` + "`" + `: Continuous Red palette mode\n` + "`" + `continuous-greens` + "`" + `: Continuous Green palette mode\n` + "`" + `continuous-purples` + "`" + `: Continuous Purple palette mode\n` + "`" + `shades` + "`" + `: Shades of a single color. Specify a single color, useful in an override rule.\n` + "`" + `fixed` + "`" + `: Fixed color mode. Specify a single color, useful in an override rule.","enum":["thresholds","palette-classic","palette-classic-by-name","continuous-viridis","continuous-magma","continuous-plasma","continuous-inferno","continuous-cividis","continuous-GrYlRd","continuous-RdYlGr","continuous-BlYlRd","continuous-YlRd","continuous-BlPu","continuous-YlBl","continuous-blues","continuous-reds","continuous-greens","continuous-purples","fixed","shades"],"type":"string"},"FieldColorSeriesByMode":{"description":"Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.","enum":["min","max","last"],"type":"string"},"FieldConfig":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"actions":{"description":"Define interactive HTTP requests that can be triggered from data visualizations.","items":{"$ref":"#/components/schemas/Action"},"type":"array"},"color":{"$ref":"#/components/schemas/FieldColor","description":"Panel color configuration"},"custom":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"custom is specified by the FieldConfig field\nin panel plugin schemas.","type":"object"},"decimals":{"description":"Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to ` + "`" + `String` + "`" + `.","type":"number"},"description":{"description":"Human readable field metadata","type":"string"},"displayName":{"description":"The display value for this field. This supports template variables blank is auto","type":"string"},"displayNameFromDS":{"description":"This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.","type":"string"},"filterable":{"description":"True if data source field supports ad-hoc filters","type":"boolean"},"links":{"description":"The behavior when clicking on a result","items":{"additionalProperties":{},"type":"object"},"type":"array"},"mappings":{"description":"Convert input values into a display string","items":{"$ref":"#/components/schemas/ValueMapping"},"type":"array"},"max":{"description":"The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"min":{"description":"The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"noValue":{"description":"Alternative to empty string","type":"string"},"path":{"description":"An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to ` + "`" + `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results","type":"string"},"thresholds":{"$ref":"#/components/schemas/ThresholdsConfig","description":"Map numeric values to states"},"unit":{"description":"Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n` + "`" + `suffix:\u003csuffix\u003e` + "`" + ` for custom unit that should go after value.\n` + "`" + `prefix:\u003cprefix\u003e` + "`" + ` for custom unit that should go before value.\n` + "`" + `time:\u003cformat\u003e` + "`" + ` For custom date time formats type for example ` + "`" + `time:YYYY-MM-DD` + "`" + `.\n` + "`" + `si:\u003cbase scale\u003e\u003cunit characters\u003e` + "`" + ` for custom SI units. For example: ` + "`" + `si: mF` + "`" + `. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n` + "`" + `count:\u003cunit\u003e` + "`" + ` for a custom count unit.\n` + "`" + `currency:\u003cunit\u003e` + "`" + ` for custom a currency unit.","type":"string"},"writeable":{"description":"True if data source can write a value to the path. Auth/authz are supported separately","type":"boolean"}},"type":"object"},"FieldConfigSource":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"defaults":{"$ref":"#/components/schemas/FieldConfig","description":"Defaults are the options applied to all fields."},"overrides":{"description":"Overrides are the options applied to specific fields overriding the defaults.","items":{"additionalProperties":false,"properties":{"__systemRef":{"description":"Describes config override rules created when interacting with Grafana.","type":"string"},"matcher":{"$ref":"#/components/schemas/MatcherConfig"},"properties":{"items":{"$ref":"#/components/schemas/DynamicConfigValue"},"type":"array"}},"required":["matcher","properties"],"type":"object"},"type":"array"}},"required":["defaults","overrides"],"type":"object"},"FilterOrigin":{"const":"dashboard","description":"Determine the origin of the adhoc variable filter","type":"string"},"GridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutItemSpec":{"additionalProperties":false,"properties":{"element":{"$ref":"#/components/schemas/ElementReference","description":"reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference"},"height":{"type":"integer"},"repeat":{"$ref":"#/components/schemas/RepeatOptions"},"width":{"type":"integer"},"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y","width","height","element"],"type":"object"},"GridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutSpec":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/GridLayoutItemKind"},"type":"array"}},"required":["items"],"type":"object"},"GroupByVariableKind":{"additionalProperties":false,"description":"Group variable kind","properties":{"kind":{"const":"GroupByVariable","type":"string"},"spec":{"$ref":"#/components/schemas/GroupByVariableSpec"}},"required":["kind","spec"],"type":"object"},"GroupByVariableSpec":{"additionalProperties":false,"description":"GroupBy variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"defaultValue":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","options","multi","hide","skipUrlSync"],"type":"object"},"HttpRequestMethod":{"enum":["GET","PUT","POST","DELETE","PATCH"],"type":"string"},"InfinityOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"datasourceUid":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url","datasourceUid"],"type":"object"},"IntervalVariableKind":{"additionalProperties":false,"description":"Interval variable kind","properties":{"kind":{"const":"IntervalVariable","type":"string"},"spec":{"$ref":"#/components/schemas/IntervalVariableSpec"}},"required":["kind","spec"],"type":"object"},"IntervalVariableSpec":{"additionalProperties":false,"description":"Interval variable specification","properties":{"auto":{"default":false,"type":"boolean"},"auto_count":{"default":0,"type":"integer"},"auto_min":{"default":"","type":"string"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","auto","auto_min","auto_count","refresh","hide","skipUrlSync"],"type":"object"},"LibraryPanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"LibraryPanel","type":"string"},"spec":{"$ref":"#/components/schemas/LibraryPanelKindSpec"}},"required":["kind","spec"],"type":"object"},"LibraryPanelKindSpec":{"additionalProperties":false,"properties":{"id":{"description":"Panel ID for the library panel in the dashboard","type":"number"},"libraryPanel":{"$ref":"#/components/schemas/LibraryPanelRef"},"title":{"description":"Title for the library panel in the dashboard","type":"string"}},"required":["id","title","libraryPanel"],"type":"object"},"LibraryPanelRef":{"additionalProperties":false,"description":"A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.","properties":{"name":{"description":"Library panel name","type":"string"},"uid":{"description":"Library panel uid","type":"string"}},"required":["name","uid"],"type":"object"},"MappingType":{"description":"Supported value mapping types\n` + "`" + `value` + "`" + `: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n` + "`" + `range` + "`" + `: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n` + "`" + `regex` + "`" + `: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n` + "`" + `special` + "`" + `: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.","enum":["value","range","regex","special"],"type":"string"},"MatcherConfig":{"additionalProperties":false,"description":"Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.","properties":{"id":{"default":"","description":"The matcher id. This is used to find the matcher implementation from registry.","type":"string"},"options":{"additionalProperties":{},"description":"The matcher options. This is specific to the matcher implementation.","type":"object"}},"required":["id"],"type":"object"},"MetricFindValue":{"additionalProperties":false,"description":"Define the MetricFindValue type","properties":{"expandable":{"type":"boolean"},"group":{"type":"string"},"text":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]}},"required":["text"],"type":"object"},"PanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"Panel","type":"string"},"spec":{"$ref":"#/components/schemas/PanelSpec"}},"required":["kind","spec"],"type":"object"},"PanelQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"PanelQuery","type":"string"},"spec":{"$ref":"#/components/schemas/PanelQuerySpec"}},"required":["kind","spec"],"type":"object"},"PanelQuerySpec":{"additionalProperties":false,"properties":{"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"hidden":{"type":"boolean"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refId":{"type":"string"}},"required":["query","refId","hidden"],"type":"object"},"PanelSpec":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/QueryGroupKind"},"description":{"type":"string"},"id":{"type":"number"},"links":{"items":{"$ref":"#/components/schemas/DataLink"},"type":"array"},"title":{"type":"string"},"transparent":{"type":"boolean"},"vizConfig":{"$ref":"#/components/schemas/VizConfigKind"}},"required":["id","title","description","links","data","vizConfig"],"type":"object"},"QueryGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"QueryGroup","type":"string"},"spec":{"$ref":"#/components/schemas/QueryGroupSpec"}},"required":["kind","spec"],"type":"object"},"QueryGroupSpec":{"additionalProperties":false,"properties":{"queries":{"items":{"$ref":"#/components/schemas/PanelQueryKind"},"type":"array"},"queryOptions":{"$ref":"#/components/schemas/QueryOptionsSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationKind"},"type":"array"}},"required":["queries","transformations","queryOptions"],"type":"object"},"QueryOptionsSpec":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"string"},"hideTimeOverride":{"type":"boolean"},"interval":{"type":"string"},"maxDataPoints":{"type":"integer"},"queryCachingTTL":{"type":"integer"},"timeFrom":{"type":"string"},"timeShift":{"type":"string"}},"type":"object"},"QueryVariableKind":{"additionalProperties":false,"description":"Query variable kind","properties":{"kind":{"const":"QueryVariable","type":"string"},"spec":{"$ref":"#/components/schemas/QueryVariableSpec"}},"required":["kind","spec"],"type":"object"},"QueryVariableSpec":{"additionalProperties":false,"description":"Query variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"definition":{"type":"string"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"placeholder":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"regexApplyTo":{"$ref":"#/components/schemas/VariableRegexApplyTo","default":"value"},"skipUrlSync":{"default":false,"type":"boolean"},"sort":{"$ref":"#/components/schemas/VariableSort"},"staticOptions":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"staticOptionsOrder":{"enum":["before","after","sorted"],"type":"string"}},"required":["name","current","hide","refresh","skipUrlSync","query","regex","sort","options","multi","includeAll","allowCustomValue"],"type":"object"},"RangeMap":{"additionalProperties":false,"description":"Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.","properties":{"options":{"additionalProperties":false,"description":"Range to match against and the result to apply when the value is within the range","properties":{"from":{"description":"Min value of the range. It can be null which means -Infinity","type":"number"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value is within the range"},"to":{"description":"Max value of the range. It can be null which means +Infinity","type":"number"}},"required":["from","to","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RegexMap":{"additionalProperties":false,"description":"Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.","properties":{"options":{"additionalProperties":false,"description":"Regular expression to match against and the result to apply when the value matches the regex","properties":{"pattern":{"description":"Regular expression to match against","type":"string"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the regex"}},"required":["pattern","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RepeatMode":{"const":"variable","description":"other repeat modes will be added in the future: label, frame","type":"string"},"RepeatOptions":{"additionalProperties":false,"properties":{"direction":{"enum":["h","v"],"type":"string"},"maxPerRow":{"type":"integer"},"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayoutRow","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutRowSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowSpec":{"additionalProperties":false,"properties":{"collapse":{"type":"boolean"},"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"fillScreen":{"type":"boolean"},"hideHeader":{"type":"boolean"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/RowRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"RowsLayoutSpec":{"additionalProperties":false,"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowsLayoutRowKind"},"type":"array"}},"required":["rows"],"type":"object"},"SpecialValueMap":{"additionalProperties":false,"description":"Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.","properties":{"options":{"additionalProperties":false,"properties":{"match":{"$ref":"#/components/schemas/SpecialValueMatch","description":"Special value to match against"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the special value"}},"required":["match","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"SpecialValueMatch":{"description":"Special value types supported by the ` + "`" + `SpecialValueMap` + "`" + `","enum":["true","false","null","nan","null+nan","empty"],"type":"string"},"SwitchVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"SwitchVariable","type":"string"},"spec":{"$ref":"#/components/schemas/SwitchVariableSpec"}},"required":["kind","spec"],"type":"object"},"SwitchVariableSpec":{"additionalProperties":false,"description":"Switch variable specification","properties":{"current":{"default":"false","type":"string"},"description":{"type":"string"},"disabledValue":{"default":"false","type":"string"},"enabledValue":{"default":"true","type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","enabledValue","disabledValue","hide","skipUrlSync"],"type":"object"},"TabRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"TabsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutSpec":{"additionalProperties":false,"properties":{"tabs":{"items":{"$ref":"#/components/schemas/TabsLayoutTabKind"},"type":"array"}},"required":["tabs"],"type":"object"},"TabsLayoutTabKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayoutTab","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutTabSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutTabSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/TabRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"TextVariableKind":{"additionalProperties":false,"description":"Text variable kind","properties":{"kind":{"const":"TextVariable","type":"string"},"spec":{"$ref":"#/components/schemas/TextVariableSpec"}},"required":["kind","spec"],"type":"object"},"TextVariableSpec":{"additionalProperties":false,"description":"Text variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","query","hide","skipUrlSync"],"type":"object"},"Threshold":{"additionalProperties":false,"properties":{"color":{"type":"string"},"value":{"description":"Value null means -Infinity","type":"number"}},"required":["value","color"],"type":"object"},"ThresholdsConfig":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ThresholdsMode"},"steps":{"items":{"$ref":"#/components/schemas/Threshold"},"type":"array"}},"required":["mode","steps"],"type":"object"},"ThresholdsMode":{"enum":["absolute","percentage"],"type":"string"},"TimeRangeOption":{"additionalProperties":false,"properties":{"display":{"default":"Last 6 hours","type":"string"},"from":{"default":"now-6h","type":"string"},"to":{"default":"now","type":"string"}},"required":["display","from","to"],"type":"object"},"TimeSettingsSpec":{"additionalProperties":false,"description":"Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.","properties":{"autoRefresh":{"default":"","description":"Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh","type":"string"},"autoRefreshIntervals":{"default":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"description":"Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals","items":{"type":"string"},"type":"array"},"fiscalYearStartMonth":{"default":0,"description":"The month that the fiscal year starts on. 0 = January, 11 = December","type":"integer"},"from":{"default":"now-6h","description":"Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"hideTimepicker":{"default":false,"description":"Whether timepicker is visible or not.\nv1: timepicker.hidden","type":"boolean"},"nowDelay":{"description":"Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay","type":"string"},"quickRanges":{"description":"Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI","items":{"$ref":"#/components/schemas/TimeRangeOption"},"type":"array"},"timezone":{"default":"browser","description":"Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".","type":"string"},"to":{"default":"now","description":"End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"weekStart":{"description":"Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".","enum":["saturday","monday","sunday"],"type":"string"}},"required":["from","to","autoRefresh","autoRefreshIntervals","hideTimepicker","fiscalYearStartMonth"],"type":"object"},"TransformationKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a TransformationKind is the transformation ID","type":"string"},"spec":{"$ref":"#/components/schemas/DataTransformerConfig"}},"required":["kind","spec"],"type":"object"},"ValueMap":{"additionalProperties":false,"description":"Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.","properties":{"options":{"additionalProperties":{"$ref":"#/components/schemas/ValueMappingResult"},"description":"Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }","type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"ValueMapping":{"oneOf":[{"$ref":"#/components/schemas/ValueMap"},{"$ref":"#/components/schemas/RangeMap"},{"$ref":"#/components/schemas/RegexMap"},{"$ref":"#/components/schemas/SpecialValueMap"}]},"ValueMappingResult":{"additionalProperties":false,"description":"Result used as replacement with text and color when the value matches","properties":{"color":{"description":"Text to use when the value matches","type":"string"},"icon":{"description":"Icon to display when the value matches. Only specific visualizations.","type":"string"},"index":{"description":"Position in the mapping array. Only used internally.","type":"integer"},"text":{"description":"Text to display when the value matches","type":"string"}},"type":"object"},"VariableHide":{"description":"Determine if the variable shows on dashboard\nAccepted values are ` + "`" + `dontHide` + "`" + ` (show label and value), ` + "`" + `hideLabel` + "`" + ` (show value only), ` + "`" + `hideVariable` + "`" + ` (show nothing).","enum":["dontHide","hideLabel","hideVariable"],"type":"string"},"VariableKind":{"oneOf":[{"$ref":"#/components/schemas/QueryVariableKind"},{"$ref":"#/components/schemas/TextVariableKind"},{"$ref":"#/components/schemas/ConstantVariableKind"},{"$ref":"#/components/schemas/DatasourceVariableKind"},{"$ref":"#/components/schemas/IntervalVariableKind"},{"$ref":"#/components/schemas/CustomVariableKind"},{"$ref":"#/components/schemas/GroupByVariableKind"},{"$ref":"#/components/schemas/AdhocVariableKind"},{"$ref":"#/components/schemas/SwitchVariableKind"}]},"VariableOption":{"additionalProperties":false,"description":"Variable option specification","properties":{"selected":{"description":"Whether the option is selected or not","type":"boolean"},"text":{"description":"Text to be displayed for the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"value":{"description":"Value of the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]}},"required":["text","value"],"type":"object"},"VariableRefresh":{"description":"Options to config when to refresh a variable\n` + "`" + `never` + "`" + `: Never refresh the variable\n` + "`" + `onDashboardLoad` + "`" + `: Queries the data source every time the dashboard loads.\n` + "`" + `onTimeRangeChanged` + "`" + `: Queries the data source when the dashboard time range changes.","enum":["never","onDashboardLoad","onTimeRangeChanged"],"type":"string"},"VariableRegexApplyTo":{"description":"Determine whether regex applies to variable value or display text\nAccepted values are ` + "`" + `value` + "`" + ` (apply to value used in queries) or ` + "`" + `text` + "`" + ` (apply to display text shown to users)","enum":["value","text"],"type":"string"},"VariableSort":{"description":"Sort variable options\nAccepted values are:\n` + "`" + `disabled` + "`" + `: No sorting\n` + "`" + `alphabeticalAsc` + "`" + `: Alphabetical ASC\n` + "`" + `alphabeticalDesc` + "`" + `: Alphabetical DESC\n` + "`" + `numericalAsc` + "`" + `: Numerical ASC\n` + "`" + `numericalDesc` + "`" + `: Numerical DESC\n` + "`" + `alphabeticalCaseInsensitiveAsc` + "`" + `: Alphabetical Case Insensitive ASC\n` + "`" + `alphabeticalCaseInsensitiveDesc` + "`" + `: Alphabetical Case Insensitive DESC\n` + "`" + `naturalAsc` + "`" + `: Natural ASC\n` + "`" + `naturalDesc` + "`" + `: Natural DESC\nVariableSort enum with default value","enum":["disabled","alphabeticalAsc","alphabeticalDesc","numericalAsc","numericalDesc","alphabeticalCaseInsensitiveAsc","alphabeticalCaseInsensitiveDesc","naturalAsc","naturalDesc"],"type":"string"},"VizConfigKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a VizConfigKind is the plugin ID","type":"string"},"spec":{"$ref":"#/components/schemas/VizConfigSpec"}},"required":["kind","spec"],"type":"object"},"VizConfigSpec":{"additionalProperties":false,"description":"--- Kinds ---","properties":{"fieldConfig":{"$ref":"#/components/schemas/FieldConfigSource"},"options":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"pluginVersion":{"type":"string"}},"required":["pluginVersion","options","fieldConfig"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationQueryKind"},"type":"array"},"cursorSync":{"$ref":"#/components/schemas/DashboardCursorSync","default":"Off","description":"Configuration of dashboard cursor sync behavior.\n\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip."},"description":{"description":"Description of dashboard.","type":"string"},"editable":{"default":true,"description":"Whether a dashboard is editable or not.","type":"boolean"},"elements":{"additionalProperties":{"$ref":"#/components/schemas/Element"},"type":"object"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"links":{"description":"Links with references to other dashboards or external websites.","items":{"$ref":"#/components/schemas/DashboardLink"},"type":"array"},"liveNow":{"description":"When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.","type":"boolean"},"preload":{"default":false,"description":"When set to true, the dashboard will load all panels in the dashboard when it's loaded.","type":"boolean"},"revision":{"description":"Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.","type":"integer"},"tags":{"description":"Tags associated with dashboard.","items":{"type":"string"},"type":"array"},"timeSettings":{"$ref":"#/components/schemas/TimeSettingsSpec"},"title":{"description":"Title of dashboard.","type":"string"},"variables":{"description":"Configured template variables.","items":{"$ref":"#/components/schemas/VariableKind"},"type":"array"}},"required":["annotations","cursorSync","elements","layout","links","preload","tags","timeSettings","title","variables"],"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`)
+ rawSchemaDashboardv2alpha1 = []byte(`{"Action":{"additionalProperties":false,"properties":{"confirmation":{"type":"string"},"fetch":{"$ref":"#/components/schemas/FetchOptions"},"infinity":{"$ref":"#/components/schemas/InfinityOptions"},"oneClick":{"type":"boolean"},"style":{"additionalProperties":false,"properties":{"backgroundColor":{"type":"string"}},"type":"object"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionType"},"variables":{"items":{"$ref":"#/components/schemas/ActionVariable"},"type":"array"}},"required":["type","title"],"type":"object"},"ActionType":{"enum":["fetch","infinity"],"type":"string"},"ActionVariable":{"additionalProperties":false,"properties":{"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionVariableType"}},"required":["key","name","type"],"type":"object"},"ActionVariableType":{"const":"string","description":"Action variable type","type":"string"},"AdHocFilterWithLabels":{"additionalProperties":false,"description":"Define the AdHocFilterWithLabels type","properties":{"condition":{"description":"@deprecated","type":"string"},"forceEdit":{"type":"boolean"},"key":{"type":"string"},"keyLabel":{"type":"string"},"operator":{"type":"string"},"origin":{"$ref":"#/components/schemas/FilterOrigin"},"value":{"type":"string"},"valueLabels":{"items":{"type":"string"},"type":"array"},"values":{"items":{"type":"string"},"type":"array"}},"required":["key","operator","value"],"type":"object"},"AdhocVariableKind":{"additionalProperties":false,"description":"Adhoc variable kind","properties":{"kind":{"const":"AdhocVariable","type":"string"},"spec":{"$ref":"#/components/schemas/AdhocVariableSpec"}},"required":["kind","spec"],"type":"object"},"AdhocVariableSpec":{"additionalProperties":false,"description":"Adhoc variable specification","properties":{"allowCustomValue":{"default":true,"type":"boolean"},"baseFilters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"defaultKeys":{"items":{"$ref":"#/components/schemas/MetricFindValue"},"type":"array"},"description":{"type":"string"},"filters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","baseFilters","filters","defaultKeys","hide","skipUrlSync","allowCustomValue"],"type":"object"},"AnnotationEventFieldMapping":{"additionalProperties":false,"description":"Annotation event field mapping. Defines how to map a data frame field to an annotation event field.","properties":{"regex":{"description":"Regular expression to apply to the field value","type":"string"},"source":{"default":"field","description":"Source type for the field value","type":"string"},"value":{"description":"Constant value to use when source is \"text\"","type":"string"}},"type":"object"},"AnnotationPanelFilter":{"additionalProperties":false,"properties":{"exclude":{"default":false,"description":"Should the specified panels be included or excluded","type":"boolean"},"ids":{"description":"Panel IDs that should be included or excluded","items":{"type":"integer"},"type":"array"}},"required":["ids"],"type":"object"},"AnnotationQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"AnnotationQuery","type":"string"},"spec":{"$ref":"#/components/schemas/AnnotationQuerySpec"}},"required":["kind","spec"],"type":"object"},"AnnotationQuerySpec":{"additionalProperties":false,"properties":{"builtIn":{"default":false,"type":"boolean"},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"enable":{"type":"boolean"},"filter":{"$ref":"#/components/schemas/AnnotationPanelFilter"},"hide":{"type":"boolean"},"iconColor":{"type":"string"},"legacyOptions":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"Catch-all field for datasource-specific properties","type":"object"},"mappings":{"additionalProperties":{"$ref":"#/components/schemas/AnnotationEventFieldMapping"},"description":"Mappings define how to convert data frame fields to annotation event fields.","type":"object"},"name":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"}},"required":["enable","hide","iconColor","name"],"type":"object"},"AutoGridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutItemSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"element":{"$ref":"#/components/schemas/ElementReference"},"repeat":{"$ref":"#/components/schemas/AutoGridRepeatOptions"}},"required":["element"],"type":"object"},"AutoGridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutSpec":{"additionalProperties":false,"properties":{"columnWidth":{"type":"number"},"columnWidthMode":{"default":"standard","enum":["narrow","standard","wide","custom"],"type":"string"},"fillScreen":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/AutoGridLayoutItemKind"},"type":"array"},"maxColumnCount":{"default":3,"type":"number"},"rowHeight":{"type":"number"},"rowHeightMode":{"default":"standard","enum":["short","standard","tall","custom"],"type":"string"}},"required":["columnWidthMode","rowHeightMode","items"],"type":"object"},"AutoGridRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"ConditionalRenderingDataKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingData","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingDataSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingDataSpec":{"additionalProperties":false,"properties":{"value":{"type":"boolean"}},"required":["value"],"type":"object"},"ConditionalRenderingGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingGroup","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingGroupSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingGroupSpec":{"additionalProperties":false,"properties":{"condition":{"enum":["and","or"],"type":"string"},"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ConditionalRenderingVariableKind"},{"$ref":"#/components/schemas/ConditionalRenderingDataKind"},{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeKind"}]},"type":"array"},"visibility":{"enum":["show","hide"],"type":"string"}},"required":["visibility","condition","items"],"type":"object"},"ConditionalRenderingTimeRangeSizeKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingTimeRangeSize","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingTimeRangeSizeSpec":{"additionalProperties":false,"properties":{"value":{"type":"string"}},"required":["value"],"type":"object"},"ConditionalRenderingVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingVariableSpec":{"additionalProperties":false,"properties":{"operator":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"value":{"type":"string"},"variable":{"type":"string"}},"required":["variable","operator","value"],"type":"object"},"ConstantVariableKind":{"additionalProperties":false,"description":"Constant variable kind","properties":{"kind":{"const":"ConstantVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConstantVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConstantVariableSpec":{"additionalProperties":false,"description":"Constant variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","hide","skipUrlSync"],"type":"object"},"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"CustomVariableKind":{"additionalProperties":false,"description":"Custom variable kind","properties":{"kind":{"const":"CustomVariable","type":"string"},"spec":{"$ref":"#/components/schemas/CustomVariableSpec"}},"required":["kind","spec"],"type":"object"},"CustomVariableSpec":{"additionalProperties":false,"description":"Custom variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"},"valuesFormat":{"enum":["csv","json"],"type":"string"}},"required":["name","query","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"DashboardCursorSync":{"description":"\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.","enum":["Crosshair","Tooltip","Off"],"type":"string"},"DashboardLink":{"additionalProperties":false,"description":"Links with references to other dashboards or external resources","properties":{"asDropdown":{"default":false,"description":"If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards","type":"boolean"},"icon":{"description":"Icon name to be displayed with the link","type":"string"},"includeVars":{"default":false,"description":"If true, includes current template variables values in the link as query params","type":"boolean"},"keepTime":{"default":false,"description":"If true, includes current time range in the link as query params","type":"boolean"},"placement":{"$ref":"#/components/schemas/DashboardLinkPlacement","description":"Placement can be used to display the link somewhere else on the dashboard other than above the visualisations."},"tags":{"description":"List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards","items":{"type":"string"},"type":"array"},"targetBlank":{"default":false,"description":"If true, the link will be opened in a new tab","type":"boolean"},"title":{"description":"Title to display with the link","type":"string"},"tooltip":{"description":"Tooltip to display when the user hovers their mouse over it","type":"string"},"type":{"$ref":"#/components/schemas/DashboardLinkType","description":"Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)\nFIXME: The type is generated as ` + "`" + `type: DashboardLinkType | dashboardLinkType.Link;` + "`" + ` but it should be ` + "`" + `type: DashboardLinkType` + "`" + `"},"url":{"description":"Link URL. Only required/valid if the type is link","type":"string"}},"required":["title","type","icon","tooltip","tags","asDropdown","targetBlank","includeVars","keepTime"],"type":"object"},"DashboardLinkPlacement":{"const":"inControlsMenu","description":"Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu","type":"string"},"DashboardLinkType":{"description":"Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)","enum":["link","dashboards"],"type":"string"},"DataLink":{"additionalProperties":false,"properties":{"targetBlank":{"type":"boolean"},"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"type":"object"},"DataQueryKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a DataQueryKind is the datasource type","type":"string"},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"}},"required":["kind","spec"],"type":"object"},"DataSourceRef":{"additionalProperties":false,"properties":{"type":{"description":"The plugin type-id","type":"string"},"uid":{"description":"Specific datasource instance","type":"string"}},"type":"object"},"DataTopic":{"description":"A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.","enum":["series","annotations","alertStates"],"type":"string"},"DataTransformerConfig":{"additionalProperties":false,"description":"Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.","properties":{"disabled":{"description":"Disabled transformations are skipped","type":"boolean"},"filter":{"$ref":"#/components/schemas/MatcherConfig","description":"Optional frame matcher. When missing it will be applied to all results"},"id":{"description":"Unique identifier of transformer","type":"string"},"options":{"additionalProperties":{},"description":"Options to be passed to the transformer\nValid options depend on the transformer id","type":"object"},"topic":{"$ref":"#/components/schemas/DataTopic","description":"Where to pull DataFrames from as input to transformation"}},"required":["id","options"],"type":"object"},"DatasourceVariableKind":{"additionalProperties":false,"description":"Datasource variable kind","properties":{"kind":{"const":"DatasourceVariable","type":"string"},"spec":{"$ref":"#/components/schemas/DatasourceVariableSpec"}},"required":["kind","spec"],"type":"object"},"DatasourceVariableSpec":{"additionalProperties":false,"description":"Datasource variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"pluginId":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","pluginId","refresh","regex","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"DynamicConfigValue":{"additionalProperties":false,"properties":{"id":{"default":"","type":"string"},"value":{"additionalProperties":{},"type":"object"}},"required":["id"],"type":"object"},"Element":{"description":"Supported dashboard elements\n|* more element types in the future","oneOf":[{"$ref":"#/components/schemas/PanelKind"},{"$ref":"#/components/schemas/LibraryPanelKind"}]},"ElementReference":{"additionalProperties":false,"properties":{"kind":{"const":"ElementReference","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},"FetchOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url"],"type":"object"},"FieldColor":{"additionalProperties":false,"description":"Map a field to a color.","properties":{"fixedColor":{"description":"The fixed color value for fixed or shades color modes.","type":"string"},"mode":{"$ref":"#/components/schemas/FieldColorModeId","description":"The main color scheme mode."},"seriesBy":{"$ref":"#/components/schemas/FieldColorSeriesByMode","description":"Some visualizations need to know how to assign a series color from by value color schemes."}},"required":["mode"],"type":"object"},"FieldColorModeId":{"description":"Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n` + "`" + `thresholds` + "`" + `: From thresholds. Informs Grafana to take the color from the matching threshold\n` + "`" + `palette-classic` + "`" + `: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `palette-classic-by-name` + "`" + `: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `continuous-viridis` + "`" + `: Continuous Viridis palette mode\n` + "`" + `continuous-magma` + "`" + `: Continuous Magma palette mode\n` + "`" + `continuous-plasma` + "`" + `: Continuous Plasma palette mode\n` + "`" + `continuous-inferno` + "`" + `: Continuous Inferno palette mode\n` + "`" + `continuous-cividis` + "`" + `: Continuous Cividis palette mode\n` + "`" + `continuous-GrYlRd` + "`" + `: Continuous Green-Yellow-Red palette mode\n` + "`" + `continuous-RdYlGr` + "`" + `: Continuous Red-Yellow-Green palette mode\n` + "`" + `continuous-BlYlRd` + "`" + `: Continuous Blue-Yellow-Red palette mode\n` + "`" + `continuous-YlRd` + "`" + `: Continuous Yellow-Red palette mode\n` + "`" + `continuous-BlPu` + "`" + `: Continuous Blue-Purple palette mode\n` + "`" + `continuous-YlBl` + "`" + `: Continuous Yellow-Blue palette mode\n` + "`" + `continuous-blues` + "`" + `: Continuous Blue palette mode\n` + "`" + `continuous-reds` + "`" + `: Continuous Red palette mode\n` + "`" + `continuous-greens` + "`" + `: Continuous Green palette mode\n` + "`" + `continuous-purples` + "`" + `: Continuous Purple palette mode\n` + "`" + `shades` + "`" + `: Shades of a single color. Specify a single color, useful in an override rule.\n` + "`" + `fixed` + "`" + `: Fixed color mode. Specify a single color, useful in an override rule.","enum":["thresholds","palette-classic","palette-classic-by-name","continuous-viridis","continuous-magma","continuous-plasma","continuous-inferno","continuous-cividis","continuous-GrYlRd","continuous-RdYlGr","continuous-BlYlRd","continuous-YlRd","continuous-BlPu","continuous-YlBl","continuous-blues","continuous-reds","continuous-greens","continuous-purples","fixed","shades"],"type":"string"},"FieldColorSeriesByMode":{"description":"Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.","enum":["min","max","last"],"type":"string"},"FieldConfig":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"actions":{"description":"Define interactive HTTP requests that can be triggered from data visualizations.","items":{"$ref":"#/components/schemas/Action"},"type":"array"},"color":{"$ref":"#/components/schemas/FieldColor","description":"Panel color configuration"},"custom":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"custom is specified by the FieldConfig field\nin panel plugin schemas.","type":"object"},"decimals":{"description":"Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to ` + "`" + `String` + "`" + `.","type":"number"},"description":{"description":"Human readable field metadata","type":"string"},"displayName":{"description":"The display value for this field. This supports template variables blank is auto","type":"string"},"displayNameFromDS":{"description":"This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.","type":"string"},"fieldMinMax":{"description":"Calculate min max per field","type":"boolean"},"filterable":{"description":"True if data source field supports ad-hoc filters","type":"boolean"},"links":{"description":"The behavior when clicking on a result","items":{"additionalProperties":{},"type":"object"},"type":"array"},"mappings":{"description":"Convert input values into a display string","items":{"$ref":"#/components/schemas/ValueMapping"},"type":"array"},"max":{"description":"The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"min":{"description":"The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"noValue":{"description":"Alternative to empty string","type":"string"},"nullValueMode":{"$ref":"#/components/schemas/NullValueMode","description":"How null values should be handled when calculating field stats\n\"null\" - Include null values, \"connected\" - Ignore nulls, \"null as zero\" - Treat nulls as zero"},"path":{"description":"An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to ` + "`" + `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results","type":"string"},"thresholds":{"$ref":"#/components/schemas/ThresholdsConfig","description":"Map numeric values to states"},"unit":{"description":"Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n` + "`" + `suffix:\u003csuffix\u003e` + "`" + ` for custom unit that should go after value.\n` + "`" + `prefix:\u003cprefix\u003e` + "`" + ` for custom unit that should go before value.\n` + "`" + `time:\u003cformat\u003e` + "`" + ` For custom date time formats type for example ` + "`" + `time:YYYY-MM-DD` + "`" + `.\n` + "`" + `si:\u003cbase scale\u003e\u003cunit characters\u003e` + "`" + ` for custom SI units. For example: ` + "`" + `si: mF` + "`" + `. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n` + "`" + `count:\u003cunit\u003e` + "`" + ` for a custom count unit.\n` + "`" + `currency:\u003cunit\u003e` + "`" + ` for custom a currency unit.","type":"string"},"writeable":{"description":"True if data source can write a value to the path. Auth/authz are supported separately","type":"boolean"}},"type":"object"},"FieldConfigSource":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"defaults":{"$ref":"#/components/schemas/FieldConfig","description":"Defaults are the options applied to all fields."},"overrides":{"description":"Overrides are the options applied to specific fields overriding the defaults.","items":{"additionalProperties":false,"properties":{"__systemRef":{"description":"Describes config override rules created when interacting with Grafana.","type":"string"},"matcher":{"$ref":"#/components/schemas/MatcherConfig"},"properties":{"items":{"$ref":"#/components/schemas/DynamicConfigValue"},"type":"array"}},"required":["matcher","properties"],"type":"object"},"type":"array"}},"required":["defaults","overrides"],"type":"object"},"FilterOrigin":{"const":"dashboard","description":"Determine the origin of the adhoc variable filter","type":"string"},"GridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutItemSpec":{"additionalProperties":false,"properties":{"element":{"$ref":"#/components/schemas/ElementReference","description":"reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference"},"height":{"type":"integer"},"repeat":{"$ref":"#/components/schemas/RepeatOptions"},"width":{"type":"integer"},"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y","width","height","element"],"type":"object"},"GridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutSpec":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/GridLayoutItemKind"},"type":"array"}},"required":["items"],"type":"object"},"GroupByVariableKind":{"additionalProperties":false,"description":"Group variable kind","properties":{"kind":{"const":"GroupByVariable","type":"string"},"spec":{"$ref":"#/components/schemas/GroupByVariableSpec"}},"required":["kind","spec"],"type":"object"},"GroupByVariableSpec":{"additionalProperties":false,"description":"GroupBy variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"defaultValue":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","options","multi","hide","skipUrlSync"],"type":"object"},"HttpRequestMethod":{"enum":["GET","PUT","POST","DELETE","PATCH"],"type":"string"},"InfinityOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"datasourceUid":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url","datasourceUid"],"type":"object"},"IntervalVariableKind":{"additionalProperties":false,"description":"Interval variable kind","properties":{"kind":{"const":"IntervalVariable","type":"string"},"spec":{"$ref":"#/components/schemas/IntervalVariableSpec"}},"required":["kind","spec"],"type":"object"},"IntervalVariableSpec":{"additionalProperties":false,"description":"Interval variable specification","properties":{"auto":{"default":false,"type":"boolean"},"auto_count":{"default":0,"type":"integer"},"auto_min":{"default":"","type":"string"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","auto","auto_min","auto_count","refresh","hide","skipUrlSync"],"type":"object"},"LibraryPanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"LibraryPanel","type":"string"},"spec":{"$ref":"#/components/schemas/LibraryPanelKindSpec"}},"required":["kind","spec"],"type":"object"},"LibraryPanelKindSpec":{"additionalProperties":false,"properties":{"id":{"description":"Panel ID for the library panel in the dashboard","type":"number"},"libraryPanel":{"$ref":"#/components/schemas/LibraryPanelRef"},"title":{"description":"Title for the library panel in the dashboard","type":"string"}},"required":["id","title","libraryPanel"],"type":"object"},"LibraryPanelRef":{"additionalProperties":false,"description":"A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.","properties":{"name":{"description":"Library panel name","type":"string"},"uid":{"description":"Library panel uid","type":"string"}},"required":["name","uid"],"type":"object"},"MappingType":{"description":"Supported value mapping types\n` + "`" + `value` + "`" + `: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n` + "`" + `range` + "`" + `: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n` + "`" + `regex` + "`" + `: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n` + "`" + `special` + "`" + `: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.","enum":["value","range","regex","special"],"type":"string"},"MatcherConfig":{"additionalProperties":false,"description":"Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.","properties":{"id":{"default":"","description":"The matcher id. This is used to find the matcher implementation from registry.","type":"string"},"options":{"additionalProperties":{},"description":"The matcher options. This is specific to the matcher implementation.","type":"object"}},"required":["id"],"type":"object"},"MetricFindValue":{"additionalProperties":false,"description":"Define the MetricFindValue type","properties":{"expandable":{"type":"boolean"},"group":{"type":"string"},"text":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]}},"required":["text"],"type":"object"},"NullValueMode":{"description":"How null values should be handled","enum":["null","connected","null as zero"],"type":"string"},"PanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"Panel","type":"string"},"spec":{"$ref":"#/components/schemas/PanelSpec"}},"required":["kind","spec"],"type":"object"},"PanelQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"PanelQuery","type":"string"},"spec":{"$ref":"#/components/schemas/PanelQuerySpec"}},"required":["kind","spec"],"type":"object"},"PanelQuerySpec":{"additionalProperties":false,"properties":{"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"hidden":{"type":"boolean"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refId":{"type":"string"}},"required":["query","refId","hidden"],"type":"object"},"PanelSpec":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/QueryGroupKind"},"description":{"type":"string"},"id":{"type":"number"},"links":{"items":{"$ref":"#/components/schemas/DataLink"},"type":"array"},"title":{"type":"string"},"transparent":{"type":"boolean"},"vizConfig":{"$ref":"#/components/schemas/VizConfigKind"}},"required":["id","title","description","links","data","vizConfig"],"type":"object"},"QueryGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"QueryGroup","type":"string"},"spec":{"$ref":"#/components/schemas/QueryGroupSpec"}},"required":["kind","spec"],"type":"object"},"QueryGroupSpec":{"additionalProperties":false,"properties":{"queries":{"items":{"$ref":"#/components/schemas/PanelQueryKind"},"type":"array"},"queryOptions":{"$ref":"#/components/schemas/QueryOptionsSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationKind"},"type":"array"}},"required":["queries","transformations","queryOptions"],"type":"object"},"QueryOptionsSpec":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"string"},"hideTimeOverride":{"type":"boolean"},"interval":{"type":"string"},"maxDataPoints":{"type":"integer"},"queryCachingTTL":{"type":"integer"},"timeFrom":{"type":"string"},"timeShift":{"type":"string"}},"type":"object"},"QueryVariableKind":{"additionalProperties":false,"description":"Query variable kind","properties":{"kind":{"const":"QueryVariable","type":"string"},"spec":{"$ref":"#/components/schemas/QueryVariableSpec"}},"required":["kind","spec"],"type":"object"},"QueryVariableSpec":{"additionalProperties":false,"description":"Query variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"definition":{"type":"string"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"placeholder":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"regexApplyTo":{"$ref":"#/components/schemas/VariableRegexApplyTo","default":"value"},"skipUrlSync":{"default":false,"type":"boolean"},"sort":{"$ref":"#/components/schemas/VariableSort"},"staticOptions":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"staticOptionsOrder":{"enum":["before","after","sorted"],"type":"string"}},"required":["name","current","hide","refresh","skipUrlSync","query","regex","sort","options","multi","includeAll","allowCustomValue"],"type":"object"},"RangeMap":{"additionalProperties":false,"description":"Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.","properties":{"options":{"additionalProperties":false,"description":"Range to match against and the result to apply when the value is within the range","properties":{"from":{"description":"Min value of the range. It can be null which means -Infinity","type":"number"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value is within the range"},"to":{"description":"Max value of the range. It can be null which means +Infinity","type":"number"}},"required":["from","to","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RegexMap":{"additionalProperties":false,"description":"Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.","properties":{"options":{"additionalProperties":false,"description":"Regular expression to match against and the result to apply when the value matches the regex","properties":{"pattern":{"description":"Regular expression to match against","type":"string"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the regex"}},"required":["pattern","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RepeatMode":{"const":"variable","description":"other repeat modes will be added in the future: label, frame","type":"string"},"RepeatOptions":{"additionalProperties":false,"properties":{"direction":{"enum":["h","v"],"type":"string"},"maxPerRow":{"type":"integer"},"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayoutRow","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutRowSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowSpec":{"additionalProperties":false,"properties":{"collapse":{"type":"boolean"},"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"fillScreen":{"type":"boolean"},"hideHeader":{"type":"boolean"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/RowRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"RowsLayoutSpec":{"additionalProperties":false,"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowsLayoutRowKind"},"type":"array"}},"required":["rows"],"type":"object"},"SpecialValueMap":{"additionalProperties":false,"description":"Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.","properties":{"options":{"additionalProperties":false,"properties":{"match":{"$ref":"#/components/schemas/SpecialValueMatch","description":"Special value to match against"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the special value"}},"required":["match","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"SpecialValueMatch":{"description":"Special value types supported by the ` + "`" + `SpecialValueMap` + "`" + `","enum":["true","false","null","nan","null+nan","empty"],"type":"string"},"SwitchVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"SwitchVariable","type":"string"},"spec":{"$ref":"#/components/schemas/SwitchVariableSpec"}},"required":["kind","spec"],"type":"object"},"SwitchVariableSpec":{"additionalProperties":false,"description":"Switch variable specification","properties":{"current":{"default":"false","type":"string"},"description":{"type":"string"},"disabledValue":{"default":"false","type":"string"},"enabledValue":{"default":"true","type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","enabledValue","disabledValue","hide","skipUrlSync"],"type":"object"},"TabRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"TabsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutSpec":{"additionalProperties":false,"properties":{"tabs":{"items":{"$ref":"#/components/schemas/TabsLayoutTabKind"},"type":"array"}},"required":["tabs"],"type":"object"},"TabsLayoutTabKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayoutTab","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutTabSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutTabSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/TabRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"TextVariableKind":{"additionalProperties":false,"description":"Text variable kind","properties":{"kind":{"const":"TextVariable","type":"string"},"spec":{"$ref":"#/components/schemas/TextVariableSpec"}},"required":["kind","spec"],"type":"object"},"TextVariableSpec":{"additionalProperties":false,"description":"Text variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","query","hide","skipUrlSync"],"type":"object"},"Threshold":{"additionalProperties":false,"properties":{"color":{"type":"string"},"value":{"description":"Value null means -Infinity","type":"number"}},"required":["value","color"],"type":"object"},"ThresholdsConfig":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ThresholdsMode"},"steps":{"items":{"$ref":"#/components/schemas/Threshold"},"type":"array"}},"required":["mode","steps"],"type":"object"},"ThresholdsMode":{"enum":["absolute","percentage"],"type":"string"},"TimeRangeOption":{"additionalProperties":false,"properties":{"display":{"default":"Last 6 hours","type":"string"},"from":{"default":"now-6h","type":"string"},"to":{"default":"now","type":"string"}},"required":["display","from","to"],"type":"object"},"TimeSettingsSpec":{"additionalProperties":false,"description":"Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.","properties":{"autoRefresh":{"default":"","description":"Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh","type":"string"},"autoRefreshIntervals":{"default":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"description":"Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals","items":{"type":"string"},"type":"array"},"fiscalYearStartMonth":{"default":0,"description":"The month that the fiscal year starts on. 0 = January, 11 = December","type":"integer"},"from":{"default":"now-6h","description":"Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"hideTimepicker":{"default":false,"description":"Whether timepicker is visible or not.\nv1: timepicker.hidden","type":"boolean"},"nowDelay":{"description":"Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay","type":"string"},"quickRanges":{"description":"Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI","items":{"$ref":"#/components/schemas/TimeRangeOption"},"type":"array"},"timezone":{"default":"browser","description":"Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".","type":"string"},"to":{"default":"now","description":"End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"weekStart":{"description":"Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".","enum":["saturday","monday","sunday"],"type":"string"}},"required":["from","to","autoRefresh","autoRefreshIntervals","hideTimepicker","fiscalYearStartMonth"],"type":"object"},"TransformationKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a TransformationKind is the transformation ID","type":"string"},"spec":{"$ref":"#/components/schemas/DataTransformerConfig"}},"required":["kind","spec"],"type":"object"},"ValueMap":{"additionalProperties":false,"description":"Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.","properties":{"options":{"additionalProperties":{"$ref":"#/components/schemas/ValueMappingResult"},"description":"Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }","type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"ValueMapping":{"oneOf":[{"$ref":"#/components/schemas/ValueMap"},{"$ref":"#/components/schemas/RangeMap"},{"$ref":"#/components/schemas/RegexMap"},{"$ref":"#/components/schemas/SpecialValueMap"}]},"ValueMappingResult":{"additionalProperties":false,"description":"Result used as replacement with text and color when the value matches","properties":{"color":{"description":"Text to use when the value matches","type":"string"},"icon":{"description":"Icon to display when the value matches. Only specific visualizations.","type":"string"},"index":{"description":"Position in the mapping array. Only used internally.","type":"integer"},"text":{"description":"Text to display when the value matches","type":"string"}},"type":"object"},"VariableHide":{"description":"Determine if the variable shows on dashboard\nAccepted values are ` + "`" + `dontHide` + "`" + ` (show label and value), ` + "`" + `hideLabel` + "`" + ` (show value only), ` + "`" + `hideVariable` + "`" + ` (show nothing).","enum":["dontHide","hideLabel","hideVariable"],"type":"string"},"VariableKind":{"oneOf":[{"$ref":"#/components/schemas/QueryVariableKind"},{"$ref":"#/components/schemas/TextVariableKind"},{"$ref":"#/components/schemas/ConstantVariableKind"},{"$ref":"#/components/schemas/DatasourceVariableKind"},{"$ref":"#/components/schemas/IntervalVariableKind"},{"$ref":"#/components/schemas/CustomVariableKind"},{"$ref":"#/components/schemas/GroupByVariableKind"},{"$ref":"#/components/schemas/AdhocVariableKind"},{"$ref":"#/components/schemas/SwitchVariableKind"}]},"VariableOption":{"additionalProperties":false,"description":"Variable option specification","properties":{"selected":{"description":"Whether the option is selected or not","type":"boolean"},"text":{"description":"Text to be displayed for the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"value":{"description":"Value of the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]}},"required":["text","value"],"type":"object"},"VariableRefresh":{"description":"Options to config when to refresh a variable\n` + "`" + `never` + "`" + `: Never refresh the variable\n` + "`" + `onDashboardLoad` + "`" + `: Queries the data source every time the dashboard loads.\n` + "`" + `onTimeRangeChanged` + "`" + `: Queries the data source when the dashboard time range changes.","enum":["never","onDashboardLoad","onTimeRangeChanged"],"type":"string"},"VariableRegexApplyTo":{"description":"Determine whether regex applies to variable value or display text\nAccepted values are ` + "`" + `value` + "`" + ` (apply to value used in queries) or ` + "`" + `text` + "`" + ` (apply to display text shown to users)","enum":["value","text"],"type":"string"},"VariableSort":{"description":"Sort variable options\nAccepted values are:\n` + "`" + `disabled` + "`" + `: No sorting\n` + "`" + `alphabeticalAsc` + "`" + `: Alphabetical ASC\n` + "`" + `alphabeticalDesc` + "`" + `: Alphabetical DESC\n` + "`" + `numericalAsc` + "`" + `: Numerical ASC\n` + "`" + `numericalDesc` + "`" + `: Numerical DESC\n` + "`" + `alphabeticalCaseInsensitiveAsc` + "`" + `: Alphabetical Case Insensitive ASC\n` + "`" + `alphabeticalCaseInsensitiveDesc` + "`" + `: Alphabetical Case Insensitive DESC\n` + "`" + `naturalAsc` + "`" + `: Natural ASC\n` + "`" + `naturalDesc` + "`" + `: Natural DESC\nVariableSort enum with default value","enum":["disabled","alphabeticalAsc","alphabeticalDesc","numericalAsc","numericalDesc","alphabeticalCaseInsensitiveAsc","alphabeticalCaseInsensitiveDesc","naturalAsc","naturalDesc"],"type":"string"},"VizConfigKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a VizConfigKind is the plugin ID","type":"string"},"spec":{"$ref":"#/components/schemas/VizConfigSpec"}},"required":["kind","spec"],"type":"object"},"VizConfigSpec":{"additionalProperties":false,"description":"--- Kinds ---","properties":{"fieldConfig":{"$ref":"#/components/schemas/FieldConfigSource"},"options":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"pluginVersion":{"type":"string"}},"required":["pluginVersion","options","fieldConfig"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationQueryKind"},"type":"array"},"cursorSync":{"$ref":"#/components/schemas/DashboardCursorSync","default":"Off","description":"Configuration of dashboard cursor sync behavior.\n\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip."},"description":{"description":"Description of dashboard.","type":"string"},"editable":{"default":true,"description":"Whether a dashboard is editable or not.","type":"boolean"},"elements":{"additionalProperties":{"$ref":"#/components/schemas/Element"},"type":"object"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"links":{"description":"Links with references to other dashboards or external websites.","items":{"$ref":"#/components/schemas/DashboardLink"},"type":"array"},"liveNow":{"description":"When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.","type":"boolean"},"preload":{"default":false,"description":"When set to true, the dashboard will load all panels in the dashboard when it's loaded.","type":"boolean"},"revision":{"description":"Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.","type":"integer"},"tags":{"description":"Tags associated with dashboard.","items":{"type":"string"},"type":"array"},"timeSettings":{"$ref":"#/components/schemas/TimeSettingsSpec"},"title":{"description":"Title of dashboard.","type":"string"},"variables":{"description":"Configured template variables.","items":{"$ref":"#/components/schemas/VariableKind"},"type":"array"}},"required":["annotations","cursorSync","elements","layout","links","preload","tags","timeSettings","title","variables"],"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`)
versionSchemaDashboardv2alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaDashboardv2alpha1, &versionSchemaDashboardv2alpha1)
- rawSchemaDashboardv2beta1 = []byte(`{"Action":{"additionalProperties":false,"properties":{"confirmation":{"type":"string"},"fetch":{"$ref":"#/components/schemas/FetchOptions"},"infinity":{"$ref":"#/components/schemas/InfinityOptions"},"oneClick":{"type":"boolean"},"style":{"additionalProperties":false,"properties":{"backgroundColor":{"type":"string"}},"type":"object"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionType"},"variables":{"items":{"$ref":"#/components/schemas/ActionVariable"},"type":"array"}},"required":["type","title"],"type":"object"},"ActionType":{"enum":["fetch","infinity"],"type":"string"},"ActionVariable":{"additionalProperties":false,"properties":{"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionVariableType"}},"required":["key","name","type"],"type":"object"},"ActionVariableType":{"const":"string","description":"Action variable type","type":"string"},"AdHocFilterWithLabels":{"additionalProperties":false,"description":"Define the AdHocFilterWithLabels type","properties":{"condition":{"description":"@deprecated","type":"string"},"forceEdit":{"type":"boolean"},"key":{"type":"string"},"keyLabel":{"type":"string"},"operator":{"type":"string"},"origin":{"$ref":"#/components/schemas/FilterOrigin"},"value":{"type":"string"},"valueLabels":{"items":{"type":"string"},"type":"array"},"values":{"items":{"type":"string"},"type":"array"}},"required":["key","operator","value"],"type":"object"},"AdhocVariableKind":{"additionalProperties":false,"description":"Adhoc variable kind","properties":{"datasource":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"AdhocVariable","type":"string"},"spec":{"$ref":"#/components/schemas/AdhocVariableSpec"}},"required":["kind","group","spec"],"type":"object"},"AdhocVariableSpec":{"additionalProperties":false,"description":"Adhoc variable specification","properties":{"allowCustomValue":{"default":true,"type":"boolean"},"baseFilters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"defaultKeys":{"items":{"$ref":"#/components/schemas/MetricFindValue"},"type":"array"},"description":{"type":"string"},"filters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","baseFilters","filters","defaultKeys","hide","skipUrlSync","allowCustomValue"],"type":"object"},"AnnotationEventFieldMapping":{"additionalProperties":false,"description":"Annotation event field mapping. Defines how to map a data frame field to an annotation event field.","properties":{"regex":{"description":"Regular expression to apply to the field value","type":"string"},"source":{"default":"field","description":"Source type for the field value","type":"string"},"value":{"description":"Constant value to use when source is \"text\"","type":"string"}},"type":"object"},"AnnotationPanelFilter":{"additionalProperties":false,"properties":{"exclude":{"default":false,"description":"Should the specified panels be included or excluded","type":"boolean"},"ids":{"description":"Panel IDs that should be included or excluded","items":{"type":"integer"},"type":"array"}},"required":["ids"],"type":"object"},"AnnotationQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"AnnotationQuery","type":"string"},"spec":{"$ref":"#/components/schemas/AnnotationQuerySpec"}},"required":["kind","spec"],"type":"object"},"AnnotationQueryPlacement":{"const":"inControlsMenu","description":"Annotation Query placement. Defines where the annotation query should be displayed.\n- \"inControlsMenu\" renders the annotation query in the dashboard controls dropdown menu","type":"string"},"AnnotationQuerySpec":{"additionalProperties":false,"properties":{"builtIn":{"default":false,"type":"boolean"},"enable":{"type":"boolean"},"filter":{"$ref":"#/components/schemas/AnnotationPanelFilter"},"hide":{"type":"boolean"},"iconColor":{"type":"string"},"legacyOptions":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"Catch-all field for datasource-specific properties. Should not be available in as code tooling.","type":"object"},"mappings":{"additionalProperties":{"$ref":"#/components/schemas/AnnotationEventFieldMapping"},"description":"Mappings define how to convert data frame fields to annotation event fields.","type":"object"},"name":{"type":"string"},"placement":{"$ref":"#/components/schemas/AnnotationQueryPlacement","description":"Placement can be used to display the annotation query somewhere else on the dashboard other than the default location."},"query":{"$ref":"#/components/schemas/DataQueryKind"}},"required":["query","enable","hide","iconColor","name"],"type":"object"},"AutoGridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutItemSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"element":{"$ref":"#/components/schemas/ElementReference"},"repeat":{"$ref":"#/components/schemas/AutoGridRepeatOptions"}},"required":["element"],"type":"object"},"AutoGridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutSpec":{"additionalProperties":false,"properties":{"columnWidth":{"type":"number"},"columnWidthMode":{"default":"standard","enum":["narrow","standard","wide","custom"],"type":"string"},"fillScreen":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/AutoGridLayoutItemKind"},"type":"array"},"maxColumnCount":{"default":3,"type":"number"},"rowHeight":{"type":"number"},"rowHeightMode":{"default":"standard","enum":["short","standard","tall","custom"],"type":"string"}},"required":["columnWidthMode","rowHeightMode","items"],"type":"object"},"AutoGridRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"ConditionalRenderingDataKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingData","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingDataSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingDataSpec":{"additionalProperties":false,"properties":{"value":{"type":"boolean"}},"required":["value"],"type":"object"},"ConditionalRenderingGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingGroup","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingGroupSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingGroupSpec":{"additionalProperties":false,"properties":{"condition":{"enum":["and","or"],"type":"string"},"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ConditionalRenderingVariableKind"},{"$ref":"#/components/schemas/ConditionalRenderingDataKind"},{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeKind"}]},"type":"array"},"visibility":{"enum":["show","hide"],"type":"string"}},"required":["visibility","condition","items"],"type":"object"},"ConditionalRenderingTimeRangeSizeKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingTimeRangeSize","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingTimeRangeSizeSpec":{"additionalProperties":false,"properties":{"value":{"type":"string"}},"required":["value"],"type":"object"},"ConditionalRenderingVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingVariableSpec":{"additionalProperties":false,"properties":{"operator":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"value":{"type":"string"},"variable":{"type":"string"}},"required":["variable","operator","value"],"type":"object"},"ConstantVariableKind":{"additionalProperties":false,"description":"Constant variable kind","properties":{"kind":{"const":"ConstantVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConstantVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConstantVariableSpec":{"additionalProperties":false,"description":"Constant variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","hide","skipUrlSync"],"type":"object"},"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"CustomVariableKind":{"additionalProperties":false,"description":"Custom variable kind","properties":{"kind":{"const":"CustomVariable","type":"string"},"spec":{"$ref":"#/components/schemas/CustomVariableSpec"}},"required":["kind","spec"],"type":"object"},"CustomVariableSpec":{"additionalProperties":false,"description":"Custom variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"},"valuesFormat":{"enum":["csv","json"],"type":"string"}},"required":["name","query","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"DashboardCursorSync":{"description":"\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.","enum":["Crosshair","Tooltip","Off"],"type":"string"},"DashboardLink":{"additionalProperties":false,"description":"Links with references to other dashboards or external resources","properties":{"asDropdown":{"default":false,"description":"If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards","type":"boolean"},"icon":{"description":"Icon name to be displayed with the link","type":"string"},"includeVars":{"default":false,"description":"If true, includes current template variables values in the link as query params","type":"boolean"},"keepTime":{"default":false,"description":"If true, includes current time range in the link as query params","type":"boolean"},"placement":{"$ref":"#/components/schemas/DashboardLinkPlacement","description":"Placement can be used to display the link somewhere else on the dashboard other than above the visualisations."},"tags":{"description":"List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards","items":{"type":"string"},"type":"array"},"targetBlank":{"default":false,"description":"If true, the link will be opened in a new tab","type":"boolean"},"title":{"description":"Title to display with the link","type":"string"},"tooltip":{"description":"Tooltip to display when the user hovers their mouse over it","type":"string"},"type":{"$ref":"#/components/schemas/DashboardLinkType","description":"Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)\nFIXME: The type is generated as ` + "`" + `type: DashboardLinkType | dashboardLinkType.Link;` + "`" + ` but it should be ` + "`" + `type: DashboardLinkType` + "`" + `"},"url":{"description":"Link URL. Only required/valid if the type is link","type":"string"}},"required":["title","type","icon","tooltip","tags","asDropdown","targetBlank","includeVars","keepTime"],"type":"object"},"DashboardLinkPlacement":{"const":"inControlsMenu","description":"Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu","type":"string"},"DashboardLinkType":{"description":"Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)","enum":["link","dashboards"],"type":"string"},"DataLink":{"additionalProperties":false,"properties":{"targetBlank":{"type":"boolean"},"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"type":"object"},"DataQueryKind":{"additionalProperties":false,"properties":{"datasource":{"additionalProperties":false,"description":"New type for datasource reference\nNot creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.","properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"DataQuery","type":"string"},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"version":{"default":"v0","type":"string"}},"required":["kind","group","version","spec"],"type":"object"},"DataTopic":{"description":"A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.","enum":["series","annotations","alertStates"],"type":"string"},"DataTransformerConfig":{"additionalProperties":false,"description":"Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.","properties":{"disabled":{"description":"Disabled transformations are skipped","type":"boolean"},"filter":{"$ref":"#/components/schemas/MatcherConfig","description":"Optional frame matcher. When missing it will be applied to all results"},"id":{"description":"Unique identifier of transformer","type":"string"},"options":{"additionalProperties":{},"description":"Options to be passed to the transformer\nValid options depend on the transformer id","type":"object"},"topic":{"$ref":"#/components/schemas/DataTopic","description":"Where to pull DataFrames from as input to transformation"}},"required":["id","options"],"type":"object"},"DatasourceVariableKind":{"additionalProperties":false,"description":"Datasource variable kind","properties":{"kind":{"const":"DatasourceVariable","type":"string"},"spec":{"$ref":"#/components/schemas/DatasourceVariableSpec"}},"required":["kind","spec"],"type":"object"},"DatasourceVariableSpec":{"additionalProperties":false,"description":"Datasource variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"pluginId":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","pluginId","refresh","regex","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"DynamicConfigValue":{"additionalProperties":false,"properties":{"id":{"default":"","type":"string"},"value":{"additionalProperties":{},"type":"object"}},"required":["id"],"type":"object"},"Element":{"description":"Supported dashboard elements\n|* more element types in the future","oneOf":[{"$ref":"#/components/schemas/PanelKind"},{"$ref":"#/components/schemas/LibraryPanelKind"}]},"ElementReference":{"additionalProperties":false,"properties":{"kind":{"const":"ElementReference","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},"FetchOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url"],"type":"object"},"FieldColor":{"additionalProperties":false,"description":"Map a field to a color.","properties":{"fixedColor":{"description":"The fixed color value for fixed or shades color modes.","type":"string"},"mode":{"$ref":"#/components/schemas/FieldColorModeId","description":"The main color scheme mode."},"seriesBy":{"$ref":"#/components/schemas/FieldColorSeriesByMode","description":"Some visualizations need to know how to assign a series color from by value color schemes."}},"required":["mode"],"type":"object"},"FieldColorModeId":{"description":"Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n` + "`" + `thresholds` + "`" + `: From thresholds. Informs Grafana to take the color from the matching threshold\n` + "`" + `palette-classic` + "`" + `: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `palette-classic-by-name` + "`" + `: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `continuous-viridis` + "`" + `: Continuous Viridis palette mode\n` + "`" + `continuous-magma` + "`" + `: Continuous Magma palette mode\n` + "`" + `continuous-plasma` + "`" + `: Continuous Plasma palette mode\n` + "`" + `continuous-inferno` + "`" + `: Continuous Inferno palette mode\n` + "`" + `continuous-cividis` + "`" + `: Continuous Cividis palette mode\n` + "`" + `continuous-GrYlRd` + "`" + `: Continuous Green-Yellow-Red palette mode\n` + "`" + `continuous-RdYlGr` + "`" + `: Continuous Red-Yellow-Green palette mode\n` + "`" + `continuous-BlYlRd` + "`" + `: Continuous Blue-Yellow-Red palette mode\n` + "`" + `continuous-YlRd` + "`" + `: Continuous Yellow-Red palette mode\n` + "`" + `continuous-BlPu` + "`" + `: Continuous Blue-Purple palette mode\n` + "`" + `continuous-YlBl` + "`" + `: Continuous Yellow-Blue palette mode\n` + "`" + `continuous-blues` + "`" + `: Continuous Blue palette mode\n` + "`" + `continuous-reds` + "`" + `: Continuous Red palette mode\n` + "`" + `continuous-greens` + "`" + `: Continuous Green palette mode\n` + "`" + `continuous-purples` + "`" + `: Continuous Purple palette mode\n` + "`" + `shades` + "`" + `: Shades of a single color. Specify a single color, useful in an override rule.\n` + "`" + `fixed` + "`" + `: Fixed color mode. Specify a single color, useful in an override rule.","enum":["thresholds","palette-classic","palette-classic-by-name","continuous-viridis","continuous-magma","continuous-plasma","continuous-inferno","continuous-cividis","continuous-GrYlRd","continuous-RdYlGr","continuous-BlYlRd","continuous-YlRd","continuous-BlPu","continuous-YlBl","continuous-blues","continuous-reds","continuous-greens","continuous-purples","fixed","shades"],"type":"string"},"FieldColorSeriesByMode":{"description":"Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.","enum":["min","max","last"],"type":"string"},"FieldConfig":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"actions":{"description":"Define interactive HTTP requests that can be triggered from data visualizations.","items":{"$ref":"#/components/schemas/Action"},"type":"array"},"color":{"$ref":"#/components/schemas/FieldColor","description":"Panel color configuration"},"custom":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"custom is specified by the FieldConfig field\nin panel plugin schemas.","type":"object"},"decimals":{"description":"Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to ` + "`" + `String` + "`" + `.","type":"number"},"description":{"description":"Human readable field metadata","type":"string"},"displayName":{"description":"The display value for this field. This supports template variables blank is auto","type":"string"},"displayNameFromDS":{"description":"This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.","type":"string"},"filterable":{"description":"True if data source field supports ad-hoc filters","type":"boolean"},"links":{"description":"The behavior when clicking on a result","items":{"additionalProperties":{},"type":"object"},"type":"array"},"mappings":{"description":"Convert input values into a display string","items":{"$ref":"#/components/schemas/ValueMapping"},"type":"array"},"max":{"description":"The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"min":{"description":"The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"noValue":{"description":"Alternative to empty string","type":"string"},"path":{"description":"An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to ` + "`" + `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results","type":"string"},"thresholds":{"$ref":"#/components/schemas/ThresholdsConfig","description":"Map numeric values to states"},"unit":{"description":"Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n` + "`" + `suffix:\u003csuffix\u003e` + "`" + ` for custom unit that should go after value.\n` + "`" + `prefix:\u003cprefix\u003e` + "`" + ` for custom unit that should go before value.\n` + "`" + `time:\u003cformat\u003e` + "`" + ` For custom date time formats type for example ` + "`" + `time:YYYY-MM-DD` + "`" + `.\n` + "`" + `si:\u003cbase scale\u003e\u003cunit characters\u003e` + "`" + ` for custom SI units. For example: ` + "`" + `si: mF` + "`" + `. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n` + "`" + `count:\u003cunit\u003e` + "`" + ` for a custom count unit.\n` + "`" + `currency:\u003cunit\u003e` + "`" + ` for custom a currency unit.","type":"string"},"writeable":{"description":"True if data source can write a value to the path. Auth/authz are supported separately","type":"boolean"}},"type":"object"},"FieldConfigSource":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"defaults":{"$ref":"#/components/schemas/FieldConfig","description":"Defaults are the options applied to all fields."},"overrides":{"description":"Overrides are the options applied to specific fields overriding the defaults.","items":{"additionalProperties":false,"properties":{"__systemRef":{"description":"Describes config override rules created when interacting with Grafana.","type":"string"},"matcher":{"$ref":"#/components/schemas/MatcherConfig"},"properties":{"items":{"$ref":"#/components/schemas/DynamicConfigValue"},"type":"array"}},"required":["matcher","properties"],"type":"object"},"type":"array"}},"required":["defaults","overrides"],"type":"object"},"FilterOrigin":{"const":"dashboard","description":"Determine the origin of the adhoc variable filter","type":"string"},"GridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutItemSpec":{"additionalProperties":false,"properties":{"element":{"$ref":"#/components/schemas/ElementReference","description":"reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference"},"height":{"type":"integer"},"repeat":{"$ref":"#/components/schemas/RepeatOptions"},"width":{"type":"integer"},"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y","width","height","element"],"type":"object"},"GridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutSpec":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/GridLayoutItemKind"},"type":"array"}},"required":["items"],"type":"object"},"GroupByVariableKind":{"additionalProperties":false,"description":"Group variable kind","properties":{"datasource":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"GroupByVariable","type":"string"},"spec":{"$ref":"#/components/schemas/GroupByVariableSpec"}},"required":["kind","group","spec"],"type":"object"},"GroupByVariableSpec":{"additionalProperties":false,"description":"GroupBy variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"defaultValue":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","options","multi","hide","skipUrlSync"],"type":"object"},"HttpRequestMethod":{"enum":["GET","PUT","POST","DELETE","PATCH"],"type":"string"},"InfinityOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"datasourceUid":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url","datasourceUid"],"type":"object"},"IntervalVariableKind":{"additionalProperties":false,"description":"Interval variable kind","properties":{"kind":{"const":"IntervalVariable","type":"string"},"spec":{"$ref":"#/components/schemas/IntervalVariableSpec"}},"required":["kind","spec"],"type":"object"},"IntervalVariableSpec":{"additionalProperties":false,"description":"Interval variable specification","properties":{"auto":{"default":false,"type":"boolean"},"auto_count":{"default":0,"type":"integer"},"auto_min":{"default":"","type":"string"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"refresh":{"const":"onTimeRangeChanged","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","auto","auto_min","auto_count","refresh","hide","skipUrlSync"],"type":"object"},"LibraryPanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"LibraryPanel","type":"string"},"spec":{"$ref":"#/components/schemas/LibraryPanelKindSpec"}},"required":["kind","spec"],"type":"object"},"LibraryPanelKindSpec":{"additionalProperties":false,"properties":{"id":{"description":"Panel ID for the library panel in the dashboard","type":"number"},"libraryPanel":{"$ref":"#/components/schemas/LibraryPanelRef"},"title":{"description":"Title for the library panel in the dashboard","type":"string"}},"required":["id","title","libraryPanel"],"type":"object"},"LibraryPanelRef":{"additionalProperties":false,"description":"A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.","properties":{"name":{"description":"Library panel name","type":"string"},"uid":{"description":"Library panel uid","type":"string"}},"required":["name","uid"],"type":"object"},"MappingType":{"description":"Supported value mapping types\n` + "`" + `value` + "`" + `: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n` + "`" + `range` + "`" + `: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n` + "`" + `regex` + "`" + `: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n` + "`" + `special` + "`" + `: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.","enum":["value","range","regex","special"],"type":"string"},"MatcherConfig":{"additionalProperties":false,"description":"Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.","properties":{"id":{"default":"","description":"The matcher id. This is used to find the matcher implementation from registry.","type":"string"},"options":{"additionalProperties":{},"description":"The matcher options. This is specific to the matcher implementation.","type":"object"}},"required":["id"],"type":"object"},"MetricFindValue":{"additionalProperties":false,"description":"Define the MetricFindValue type","properties":{"expandable":{"type":"boolean"},"group":{"type":"string"},"text":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]}},"required":["text"],"type":"object"},"PanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"Panel","type":"string"},"spec":{"$ref":"#/components/schemas/PanelSpec"}},"required":["kind","spec"],"type":"object"},"PanelQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"PanelQuery","type":"string"},"spec":{"$ref":"#/components/schemas/PanelQuerySpec"}},"required":["kind","spec"],"type":"object"},"PanelQuerySpec":{"additionalProperties":false,"properties":{"hidden":{"type":"boolean"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refId":{"default":"A","type":"string"}},"required":["query","refId","hidden"],"type":"object"},"PanelSpec":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/QueryGroupKind"},"description":{"type":"string"},"id":{"type":"number"},"links":{"items":{"$ref":"#/components/schemas/DataLink"},"type":"array"},"title":{"type":"string"},"transparent":{"type":"boolean"},"vizConfig":{"$ref":"#/components/schemas/VizConfigKind"}},"required":["id","title","description","links","data","vizConfig"],"type":"object"},"QueryGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"QueryGroup","type":"string"},"spec":{"$ref":"#/components/schemas/QueryGroupSpec"}},"required":["kind","spec"],"type":"object"},"QueryGroupSpec":{"additionalProperties":false,"properties":{"queries":{"items":{"$ref":"#/components/schemas/PanelQueryKind"},"type":"array"},"queryOptions":{"$ref":"#/components/schemas/QueryOptionsSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationKind"},"type":"array"}},"required":["queries","transformations","queryOptions"],"type":"object"},"QueryOptionsSpec":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"string"},"hideTimeOverride":{"type":"boolean"},"interval":{"type":"string"},"maxDataPoints":{"type":"integer"},"queryCachingTTL":{"type":"integer"},"timeCompare":{"type":"string"},"timeFrom":{"type":"string"},"timeShift":{"type":"string"}},"type":"object"},"QueryVariableKind":{"additionalProperties":false,"description":"Query variable kind","properties":{"kind":{"const":"QueryVariable","type":"string"},"spec":{"$ref":"#/components/schemas/QueryVariableSpec"}},"required":["kind","spec"],"type":"object"},"QueryVariableSpec":{"additionalProperties":false,"description":"Query variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"definition":{"type":"string"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"placeholder":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"regexApplyTo":{"$ref":"#/components/schemas/VariableRegexApplyTo","default":"value"},"skipUrlSync":{"default":false,"type":"boolean"},"sort":{"$ref":"#/components/schemas/VariableSort"},"staticOptions":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"staticOptionsOrder":{"enum":["before","after","sorted"],"type":"string"}},"required":["name","current","hide","refresh","skipUrlSync","query","regex","sort","options","multi","includeAll","allowCustomValue"],"type":"object"},"RangeMap":{"additionalProperties":false,"description":"Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.","properties":{"options":{"additionalProperties":false,"description":"Range to match against and the result to apply when the value is within the range","properties":{"from":{"description":"Min value of the range. It can be null which means -Infinity","type":"number"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value is within the range"},"to":{"description":"Max value of the range. It can be null which means +Infinity","type":"number"}},"required":["from","to","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RegexMap":{"additionalProperties":false,"description":"Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.","properties":{"options":{"additionalProperties":false,"description":"Regular expression to match against and the result to apply when the value matches the regex","properties":{"pattern":{"description":"Regular expression to match against","type":"string"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the regex"}},"required":["pattern","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RepeatMode":{"const":"variable","description":"other repeat modes will be added in the future: label, frame","type":"string"},"RepeatOptions":{"additionalProperties":false,"properties":{"direction":{"enum":["h","v"],"type":"string"},"maxPerRow":{"type":"integer"},"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayoutRow","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutRowSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowSpec":{"additionalProperties":false,"properties":{"collapse":{"type":"boolean"},"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"fillScreen":{"type":"boolean"},"hideHeader":{"type":"boolean"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/RowRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"RowsLayoutSpec":{"additionalProperties":false,"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowsLayoutRowKind"},"type":"array"}},"required":["rows"],"type":"object"},"SpecialValueMap":{"additionalProperties":false,"description":"Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.","properties":{"options":{"additionalProperties":false,"properties":{"match":{"$ref":"#/components/schemas/SpecialValueMatch","description":"Special value to match against"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the special value"}},"required":["match","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"SpecialValueMatch":{"description":"Special value types supported by the ` + "`" + `SpecialValueMap` + "`" + `","enum":["true","false","null","nan","null+nan","empty"],"type":"string"},"SwitchVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"SwitchVariable","type":"string"},"spec":{"$ref":"#/components/schemas/SwitchVariableSpec"}},"required":["kind","spec"],"type":"object"},"SwitchVariableSpec":{"additionalProperties":false,"properties":{"current":{"default":"false","type":"string"},"description":{"type":"string"},"disabledValue":{"default":"false","type":"string"},"enabledValue":{"default":"true","type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","enabledValue","disabledValue","hide","skipUrlSync"],"type":"object"},"TabRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"TabsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutSpec":{"additionalProperties":false,"properties":{"tabs":{"items":{"$ref":"#/components/schemas/TabsLayoutTabKind"},"type":"array"}},"required":["tabs"],"type":"object"},"TabsLayoutTabKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayoutTab","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutTabSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutTabSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/TabRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"TextVariableKind":{"additionalProperties":false,"description":"Text variable kind","properties":{"kind":{"const":"TextVariable","type":"string"},"spec":{"$ref":"#/components/schemas/TextVariableSpec"}},"required":["kind","spec"],"type":"object"},"TextVariableSpec":{"additionalProperties":false,"description":"Text variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","query","hide","skipUrlSync"],"type":"object"},"Threshold":{"additionalProperties":false,"properties":{"color":{"type":"string"},"value":{"description":"Value null means -Infinity","type":"number"}},"required":["value","color"],"type":"object"},"ThresholdsConfig":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ThresholdsMode"},"steps":{"items":{"$ref":"#/components/schemas/Threshold"},"type":"array"}},"required":["mode","steps"],"type":"object"},"ThresholdsMode":{"enum":["absolute","percentage"],"type":"string"},"TimeRangeOption":{"additionalProperties":false,"properties":{"display":{"default":"Last 6 hours","type":"string"},"from":{"default":"now-6h","type":"string"},"to":{"default":"now","type":"string"}},"required":["display","from","to"],"type":"object"},"TimeSettingsSpec":{"additionalProperties":false,"description":"Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.","properties":{"autoRefresh":{"default":"","description":"Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh","type":"string"},"autoRefreshIntervals":{"default":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"description":"Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals","items":{"type":"string"},"type":"array"},"fiscalYearStartMonth":{"default":0,"description":"The month that the fiscal year starts on. 0 = January, 11 = December","type":"integer"},"from":{"default":"now-6h","description":"Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"hideTimepicker":{"default":false,"description":"Whether timepicker is visible or not.\nv1: timepicker.hidden","type":"boolean"},"nowDelay":{"description":"Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay","type":"string"},"quickRanges":{"description":"Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI","items":{"$ref":"#/components/schemas/TimeRangeOption"},"type":"array"},"timezone":{"default":"browser","description":"Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".","type":"string"},"to":{"default":"now","description":"End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"weekStart":{"description":"Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".","enum":["saturday","monday","sunday"],"type":"string"}},"required":["from","to","autoRefresh","autoRefreshIntervals","hideTimepicker","fiscalYearStartMonth"],"type":"object"},"TransformationKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a TransformationKind is the transformation ID","type":"string"},"spec":{"$ref":"#/components/schemas/DataTransformerConfig"}},"required":["kind","spec"],"type":"object"},"ValueMap":{"additionalProperties":false,"description":"Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.","properties":{"options":{"additionalProperties":{"$ref":"#/components/schemas/ValueMappingResult"},"description":"Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }","type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"ValueMapping":{"oneOf":[{"$ref":"#/components/schemas/ValueMap"},{"$ref":"#/components/schemas/RangeMap"},{"$ref":"#/components/schemas/RegexMap"},{"$ref":"#/components/schemas/SpecialValueMap"}]},"ValueMappingResult":{"additionalProperties":false,"description":"Result used as replacement with text and color when the value matches","properties":{"color":{"description":"Text to use when the value matches","type":"string"},"icon":{"description":"Icon to display when the value matches. Only specific visualizations.","type":"string"},"index":{"description":"Position in the mapping array. Only used internally.","type":"integer"},"text":{"description":"Text to display when the value matches","type":"string"}},"type":"object"},"VariableHide":{"description":"Determine if the variable shows on dashboard\nAccepted values are ` + "`" + `dontHide` + "`" + ` (show label and value), ` + "`" + `hideLabel` + "`" + ` (show value only), ` + "`" + `hideVariable` + "`" + ` (show nothing), ` + "`" + `inControlsMenu` + "`" + ` (show in a drop-down menu).","enum":["dontHide","hideLabel","hideVariable","inControlsMenu"],"type":"string"},"VariableKind":{"oneOf":[{"$ref":"#/components/schemas/QueryVariableKind"},{"$ref":"#/components/schemas/TextVariableKind"},{"$ref":"#/components/schemas/ConstantVariableKind"},{"$ref":"#/components/schemas/DatasourceVariableKind"},{"$ref":"#/components/schemas/IntervalVariableKind"},{"$ref":"#/components/schemas/CustomVariableKind"},{"$ref":"#/components/schemas/GroupByVariableKind"},{"$ref":"#/components/schemas/AdhocVariableKind"},{"$ref":"#/components/schemas/SwitchVariableKind"}]},"VariableOption":{"additionalProperties":false,"description":"Variable option specification","properties":{"selected":{"description":"Whether the option is selected or not","type":"boolean"},"text":{"description":"Text to be displayed for the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"value":{"description":"Value of the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]}},"required":["text","value"],"type":"object"},"VariableRefresh":{"description":"Options to config when to refresh a variable\n` + "`" + `never` + "`" + `: Never refresh the variable\n` + "`" + `onDashboardLoad` + "`" + `: Queries the data source every time the dashboard loads.\n` + "`" + `onTimeRangeChanged` + "`" + `: Queries the data source when the dashboard time range changes.","enum":["never","onDashboardLoad","onTimeRangeChanged"],"type":"string"},"VariableRegexApplyTo":{"description":"Determine whether regex applies to variable value or display text\nAccepted values are ` + "`" + `value` + "`" + ` (apply to value used in queries) or ` + "`" + `text` + "`" + ` (apply to display text shown to users)","enum":["value","text"],"type":"string"},"VariableSort":{"description":"Sort variable options\nAccepted values are:\n` + "`" + `disabled` + "`" + `: No sorting\n` + "`" + `alphabeticalAsc` + "`" + `: Alphabetical ASC\n` + "`" + `alphabeticalDesc` + "`" + `: Alphabetical DESC\n` + "`" + `numericalAsc` + "`" + `: Numerical ASC\n` + "`" + `numericalDesc` + "`" + `: Numerical DESC\n` + "`" + `alphabeticalCaseInsensitiveAsc` + "`" + `: Alphabetical Case Insensitive ASC\n` + "`" + `alphabeticalCaseInsensitiveDesc` + "`" + `: Alphabetical Case Insensitive DESC\n` + "`" + `naturalAsc` + "`" + `: Natural ASC\n` + "`" + `naturalDesc` + "`" + `: Natural DESC\nVariableSort enum with default value","enum":["disabled","alphabeticalAsc","alphabeticalDesc","numericalAsc","numericalDesc","alphabeticalCaseInsensitiveAsc","alphabeticalCaseInsensitiveDesc","naturalAsc","naturalDesc"],"type":"string"},"VizConfigKind":{"additionalProperties":false,"properties":{"group":{"description":"The group is the plugin ID","type":"string"},"kind":{"const":"VizConfig","type":"string"},"spec":{"$ref":"#/components/schemas/VizConfigSpec"},"version":{"type":"string"}},"required":["kind","group","version","spec"],"type":"object"},"VizConfigSpec":{"additionalProperties":false,"description":"--- Kinds ---","properties":{"fieldConfig":{"$ref":"#/components/schemas/FieldConfigSource"},"options":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"}},"required":["options","fieldConfig"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationQueryKind"},"type":"array"},"cursorSync":{"$ref":"#/components/schemas/DashboardCursorSync","default":"Off","description":"Configuration of dashboard cursor sync behavior.\n\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip."},"description":{"description":"Description of dashboard.","type":"string"},"editable":{"default":true,"description":"Whether a dashboard is editable or not.","type":"boolean"},"elements":{"additionalProperties":{"$ref":"#/components/schemas/Element"},"type":"object"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"links":{"description":"Links with references to other dashboards or external websites.","items":{"$ref":"#/components/schemas/DashboardLink"},"type":"array"},"liveNow":{"description":"When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.","type":"boolean"},"preload":{"default":false,"description":"When set to true, the dashboard will load all panels in the dashboard when it's loaded.","type":"boolean"},"revision":{"description":"Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.","type":"integer"},"tags":{"description":"Tags associated with dashboard.","items":{"type":"string"},"type":"array"},"timeSettings":{"$ref":"#/components/schemas/TimeSettingsSpec"},"title":{"description":"Title of dashboard.","type":"string"},"variables":{"description":"Configured template variables.","items":{"$ref":"#/components/schemas/VariableKind"},"type":"array"}},"required":["annotations","cursorSync","elements","layout","links","preload","tags","timeSettings","title","variables"],"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`)
+ rawSchemaDashboardv2beta1 = []byte(`{"Action":{"additionalProperties":false,"properties":{"confirmation":{"type":"string"},"fetch":{"$ref":"#/components/schemas/FetchOptions"},"infinity":{"$ref":"#/components/schemas/InfinityOptions"},"oneClick":{"type":"boolean"},"style":{"additionalProperties":false,"properties":{"backgroundColor":{"type":"string"}},"type":"object"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionType"},"variables":{"items":{"$ref":"#/components/schemas/ActionVariable"},"type":"array"}},"required":["type","title"],"type":"object"},"ActionType":{"enum":["fetch","infinity"],"type":"string"},"ActionVariable":{"additionalProperties":false,"properties":{"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionVariableType"}},"required":["key","name","type"],"type":"object"},"ActionVariableType":{"const":"string","description":"Action variable type","type":"string"},"AdHocFilterWithLabels":{"additionalProperties":false,"description":"Define the AdHocFilterWithLabels type","properties":{"condition":{"description":"@deprecated","type":"string"},"forceEdit":{"type":"boolean"},"key":{"type":"string"},"keyLabel":{"type":"string"},"operator":{"type":"string"},"origin":{"$ref":"#/components/schemas/FilterOrigin"},"value":{"type":"string"},"valueLabels":{"items":{"type":"string"},"type":"array"},"values":{"items":{"type":"string"},"type":"array"}},"required":["key","operator","value"],"type":"object"},"AdhocVariableKind":{"additionalProperties":false,"description":"Adhoc variable kind","properties":{"datasource":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"AdhocVariable","type":"string"},"spec":{"$ref":"#/components/schemas/AdhocVariableSpec"}},"required":["kind","group","spec"],"type":"object"},"AdhocVariableSpec":{"additionalProperties":false,"description":"Adhoc variable specification","properties":{"allowCustomValue":{"default":true,"type":"boolean"},"baseFilters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"defaultKeys":{"items":{"$ref":"#/components/schemas/MetricFindValue"},"type":"array"},"description":{"type":"string"},"filters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","baseFilters","filters","defaultKeys","hide","skipUrlSync","allowCustomValue"],"type":"object"},"AnnotationEventFieldMapping":{"additionalProperties":false,"description":"Annotation event field mapping. Defines how to map a data frame field to an annotation event field.","properties":{"regex":{"description":"Regular expression to apply to the field value","type":"string"},"source":{"default":"field","description":"Source type for the field value","type":"string"},"value":{"description":"Constant value to use when source is \"text\"","type":"string"}},"type":"object"},"AnnotationPanelFilter":{"additionalProperties":false,"properties":{"exclude":{"default":false,"description":"Should the specified panels be included or excluded","type":"boolean"},"ids":{"description":"Panel IDs that should be included or excluded","items":{"type":"integer"},"type":"array"}},"required":["ids"],"type":"object"},"AnnotationQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"AnnotationQuery","type":"string"},"spec":{"$ref":"#/components/schemas/AnnotationQuerySpec"}},"required":["kind","spec"],"type":"object"},"AnnotationQueryPlacement":{"const":"inControlsMenu","description":"Annotation Query placement. Defines where the annotation query should be displayed.\n- \"inControlsMenu\" renders the annotation query in the dashboard controls dropdown menu","type":"string"},"AnnotationQuerySpec":{"additionalProperties":false,"properties":{"builtIn":{"default":false,"type":"boolean"},"enable":{"type":"boolean"},"filter":{"$ref":"#/components/schemas/AnnotationPanelFilter"},"hide":{"type":"boolean"},"iconColor":{"type":"string"},"legacyOptions":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"Catch-all field for datasource-specific properties. Should not be available in as code tooling.","type":"object"},"mappings":{"additionalProperties":{"$ref":"#/components/schemas/AnnotationEventFieldMapping"},"description":"Mappings define how to convert data frame fields to annotation event fields.","type":"object"},"name":{"type":"string"},"placement":{"$ref":"#/components/schemas/AnnotationQueryPlacement","description":"Placement can be used to display the annotation query somewhere else on the dashboard other than the default location."},"query":{"$ref":"#/components/schemas/DataQueryKind"}},"required":["query","enable","hide","iconColor","name"],"type":"object"},"AutoGridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutItemSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"element":{"$ref":"#/components/schemas/ElementReference"},"repeat":{"$ref":"#/components/schemas/AutoGridRepeatOptions"}},"required":["element"],"type":"object"},"AutoGridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutSpec":{"additionalProperties":false,"properties":{"columnWidth":{"type":"number"},"columnWidthMode":{"default":"standard","enum":["narrow","standard","wide","custom"],"type":"string"},"fillScreen":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/AutoGridLayoutItemKind"},"type":"array"},"maxColumnCount":{"default":3,"type":"number"},"rowHeight":{"type":"number"},"rowHeightMode":{"default":"standard","enum":["short","standard","tall","custom"],"type":"string"}},"required":["columnWidthMode","rowHeightMode","items"],"type":"object"},"AutoGridRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"ConditionalRenderingDataKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingData","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingDataSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingDataSpec":{"additionalProperties":false,"properties":{"value":{"type":"boolean"}},"required":["value"],"type":"object"},"ConditionalRenderingGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingGroup","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingGroupSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingGroupSpec":{"additionalProperties":false,"properties":{"condition":{"enum":["and","or"],"type":"string"},"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ConditionalRenderingVariableKind"},{"$ref":"#/components/schemas/ConditionalRenderingDataKind"},{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeKind"}]},"type":"array"},"visibility":{"enum":["show","hide"],"type":"string"}},"required":["visibility","condition","items"],"type":"object"},"ConditionalRenderingTimeRangeSizeKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingTimeRangeSize","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingTimeRangeSizeSpec":{"additionalProperties":false,"properties":{"value":{"type":"string"}},"required":["value"],"type":"object"},"ConditionalRenderingVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingVariableSpec":{"additionalProperties":false,"properties":{"operator":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"value":{"type":"string"},"variable":{"type":"string"}},"required":["variable","operator","value"],"type":"object"},"ConstantVariableKind":{"additionalProperties":false,"description":"Constant variable kind","properties":{"kind":{"const":"ConstantVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConstantVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConstantVariableSpec":{"additionalProperties":false,"description":"Constant variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","hide","skipUrlSync"],"type":"object"},"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"CustomVariableKind":{"additionalProperties":false,"description":"Custom variable kind","properties":{"kind":{"const":"CustomVariable","type":"string"},"spec":{"$ref":"#/components/schemas/CustomVariableSpec"}},"required":["kind","spec"],"type":"object"},"CustomVariableSpec":{"additionalProperties":false,"description":"Custom variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"},"valuesFormat":{"enum":["csv","json"],"type":"string"}},"required":["name","query","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"DashboardCursorSync":{"description":"\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.","enum":["Crosshair","Tooltip","Off"],"type":"string"},"DashboardLink":{"additionalProperties":false,"description":"Links with references to other dashboards or external resources","properties":{"asDropdown":{"default":false,"description":"If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards","type":"boolean"},"icon":{"description":"Icon name to be displayed with the link","type":"string"},"includeVars":{"default":false,"description":"If true, includes current template variables values in the link as query params","type":"boolean"},"keepTime":{"default":false,"description":"If true, includes current time range in the link as query params","type":"boolean"},"placement":{"$ref":"#/components/schemas/DashboardLinkPlacement","description":"Placement can be used to display the link somewhere else on the dashboard other than above the visualisations."},"tags":{"description":"List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards","items":{"type":"string"},"type":"array"},"targetBlank":{"default":false,"description":"If true, the link will be opened in a new tab","type":"boolean"},"title":{"description":"Title to display with the link","type":"string"},"tooltip":{"description":"Tooltip to display when the user hovers their mouse over it","type":"string"},"type":{"$ref":"#/components/schemas/DashboardLinkType","description":"Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)\nFIXME: The type is generated as ` + "`" + `type: DashboardLinkType | dashboardLinkType.Link;` + "`" + ` but it should be ` + "`" + `type: DashboardLinkType` + "`" + `"},"url":{"description":"Link URL. Only required/valid if the type is link","type":"string"}},"required":["title","type","icon","tooltip","tags","asDropdown","targetBlank","includeVars","keepTime"],"type":"object"},"DashboardLinkPlacement":{"const":"inControlsMenu","description":"Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu","type":"string"},"DashboardLinkType":{"description":"Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)","enum":["link","dashboards"],"type":"string"},"DataLink":{"additionalProperties":false,"properties":{"targetBlank":{"type":"boolean"},"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"type":"object"},"DataQueryKind":{"additionalProperties":false,"properties":{"datasource":{"additionalProperties":false,"description":"New type for datasource reference\nNot creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.","properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"DataQuery","type":"string"},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"version":{"default":"v0","type":"string"}},"required":["kind","group","version","spec"],"type":"object"},"DataTopic":{"description":"A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.","enum":["series","annotations","alertStates"],"type":"string"},"DataTransformerConfig":{"additionalProperties":false,"description":"Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.","properties":{"disabled":{"description":"Disabled transformations are skipped","type":"boolean"},"filter":{"$ref":"#/components/schemas/MatcherConfig","description":"Optional frame matcher. When missing it will be applied to all results"},"id":{"description":"Unique identifier of transformer","type":"string"},"options":{"additionalProperties":{},"description":"Options to be passed to the transformer\nValid options depend on the transformer id","type":"object"},"topic":{"$ref":"#/components/schemas/DataTopic","description":"Where to pull DataFrames from as input to transformation"}},"required":["id","options"],"type":"object"},"DatasourceVariableKind":{"additionalProperties":false,"description":"Datasource variable kind","properties":{"kind":{"const":"DatasourceVariable","type":"string"},"spec":{"$ref":"#/components/schemas/DatasourceVariableSpec"}},"required":["kind","spec"],"type":"object"},"DatasourceVariableSpec":{"additionalProperties":false,"description":"Datasource variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"pluginId":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","pluginId","refresh","regex","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"DynamicConfigValue":{"additionalProperties":false,"properties":{"id":{"default":"","type":"string"},"value":{"additionalProperties":{},"type":"object"}},"required":["id"],"type":"object"},"Element":{"description":"Supported dashboard elements\n|* more element types in the future","oneOf":[{"$ref":"#/components/schemas/PanelKind"},{"$ref":"#/components/schemas/LibraryPanelKind"}]},"ElementReference":{"additionalProperties":false,"properties":{"kind":{"const":"ElementReference","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},"FetchOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url"],"type":"object"},"FieldColor":{"additionalProperties":false,"description":"Map a field to a color.","properties":{"fixedColor":{"description":"The fixed color value for fixed or shades color modes.","type":"string"},"mode":{"$ref":"#/components/schemas/FieldColorModeId","description":"The main color scheme mode."},"seriesBy":{"$ref":"#/components/schemas/FieldColorSeriesByMode","description":"Some visualizations need to know how to assign a series color from by value color schemes."}},"required":["mode"],"type":"object"},"FieldColorModeId":{"description":"Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n` + "`" + `thresholds` + "`" + `: From thresholds. Informs Grafana to take the color from the matching threshold\n` + "`" + `palette-classic` + "`" + `: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `palette-classic-by-name` + "`" + `: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `continuous-viridis` + "`" + `: Continuous Viridis palette mode\n` + "`" + `continuous-magma` + "`" + `: Continuous Magma palette mode\n` + "`" + `continuous-plasma` + "`" + `: Continuous Plasma palette mode\n` + "`" + `continuous-inferno` + "`" + `: Continuous Inferno palette mode\n` + "`" + `continuous-cividis` + "`" + `: Continuous Cividis palette mode\n` + "`" + `continuous-GrYlRd` + "`" + `: Continuous Green-Yellow-Red palette mode\n` + "`" + `continuous-RdYlGr` + "`" + `: Continuous Red-Yellow-Green palette mode\n` + "`" + `continuous-BlYlRd` + "`" + `: Continuous Blue-Yellow-Red palette mode\n` + "`" + `continuous-YlRd` + "`" + `: Continuous Yellow-Red palette mode\n` + "`" + `continuous-BlPu` + "`" + `: Continuous Blue-Purple palette mode\n` + "`" + `continuous-YlBl` + "`" + `: Continuous Yellow-Blue palette mode\n` + "`" + `continuous-blues` + "`" + `: Continuous Blue palette mode\n` + "`" + `continuous-reds` + "`" + `: Continuous Red palette mode\n` + "`" + `continuous-greens` + "`" + `: Continuous Green palette mode\n` + "`" + `continuous-purples` + "`" + `: Continuous Purple palette mode\n` + "`" + `shades` + "`" + `: Shades of a single color. Specify a single color, useful in an override rule.\n` + "`" + `fixed` + "`" + `: Fixed color mode. Specify a single color, useful in an override rule.","enum":["thresholds","palette-classic","palette-classic-by-name","continuous-viridis","continuous-magma","continuous-plasma","continuous-inferno","continuous-cividis","continuous-GrYlRd","continuous-RdYlGr","continuous-BlYlRd","continuous-YlRd","continuous-BlPu","continuous-YlBl","continuous-blues","continuous-reds","continuous-greens","continuous-purples","fixed","shades"],"type":"string"},"FieldColorSeriesByMode":{"description":"Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.","enum":["min","max","last"],"type":"string"},"FieldConfig":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"actions":{"description":"Define interactive HTTP requests that can be triggered from data visualizations.","items":{"$ref":"#/components/schemas/Action"},"type":"array"},"color":{"$ref":"#/components/schemas/FieldColor","description":"Panel color configuration"},"custom":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"custom is specified by the FieldConfig field\nin panel plugin schemas.","type":"object"},"decimals":{"description":"Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to ` + "`" + `String` + "`" + `.","type":"number"},"description":{"description":"Human readable field metadata","type":"string"},"displayName":{"description":"The display value for this field. This supports template variables blank is auto","type":"string"},"displayNameFromDS":{"description":"This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.","type":"string"},"fieldMinMax":{"description":"Calculate min max per field","type":"boolean"},"filterable":{"description":"True if data source field supports ad-hoc filters","type":"boolean"},"links":{"description":"The behavior when clicking on a result","items":{"additionalProperties":{},"type":"object"},"type":"array"},"mappings":{"description":"Convert input values into a display string","items":{"$ref":"#/components/schemas/ValueMapping"},"type":"array"},"max":{"description":"The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"min":{"description":"The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"noValue":{"description":"Alternative to empty string","type":"string"},"nullValueMode":{"$ref":"#/components/schemas/NullValueMode","description":"How null values should be handled when calculating field stats\n\"null\" - Include null values, \"connected\" - Ignore nulls, \"null as zero\" - Treat nulls as zero"},"path":{"description":"An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to ` + "`" + `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results","type":"string"},"thresholds":{"$ref":"#/components/schemas/ThresholdsConfig","description":"Map numeric values to states"},"unit":{"description":"Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n` + "`" + `suffix:\u003csuffix\u003e` + "`" + ` for custom unit that should go after value.\n` + "`" + `prefix:\u003cprefix\u003e` + "`" + ` for custom unit that should go before value.\n` + "`" + `time:\u003cformat\u003e` + "`" + ` For custom date time formats type for example ` + "`" + `time:YYYY-MM-DD` + "`" + `.\n` + "`" + `si:\u003cbase scale\u003e\u003cunit characters\u003e` + "`" + ` for custom SI units. For example: ` + "`" + `si: mF` + "`" + `. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n` + "`" + `count:\u003cunit\u003e` + "`" + ` for a custom count unit.\n` + "`" + `currency:\u003cunit\u003e` + "`" + ` for custom a currency unit.","type":"string"},"writeable":{"description":"True if data source can write a value to the path. Auth/authz are supported separately","type":"boolean"}},"type":"object"},"FieldConfigSource":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"defaults":{"$ref":"#/components/schemas/FieldConfig","description":"Defaults are the options applied to all fields."},"overrides":{"description":"Overrides are the options applied to specific fields overriding the defaults.","items":{"additionalProperties":false,"properties":{"__systemRef":{"description":"Describes config override rules created when interacting with Grafana.","type":"string"},"matcher":{"$ref":"#/components/schemas/MatcherConfig"},"properties":{"items":{"$ref":"#/components/schemas/DynamicConfigValue"},"type":"array"}},"required":["matcher","properties"],"type":"object"},"type":"array"}},"required":["defaults","overrides"],"type":"object"},"FilterOrigin":{"const":"dashboard","description":"Determine the origin of the adhoc variable filter","type":"string"},"GridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutItemSpec":{"additionalProperties":false,"properties":{"element":{"$ref":"#/components/schemas/ElementReference","description":"reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference"},"height":{"type":"integer"},"repeat":{"$ref":"#/components/schemas/RepeatOptions"},"width":{"type":"integer"},"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y","width","height","element"],"type":"object"},"GridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutSpec":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/GridLayoutItemKind"},"type":"array"}},"required":["items"],"type":"object"},"GroupByVariableKind":{"additionalProperties":false,"description":"Group variable kind","properties":{"datasource":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"GroupByVariable","type":"string"},"spec":{"$ref":"#/components/schemas/GroupByVariableSpec"}},"required":["kind","group","spec"],"type":"object"},"GroupByVariableSpec":{"additionalProperties":false,"description":"GroupBy variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"defaultValue":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","options","multi","hide","skipUrlSync"],"type":"object"},"HttpRequestMethod":{"enum":["GET","PUT","POST","DELETE","PATCH"],"type":"string"},"InfinityOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"datasourceUid":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url","datasourceUid"],"type":"object"},"IntervalVariableKind":{"additionalProperties":false,"description":"Interval variable kind","properties":{"kind":{"const":"IntervalVariable","type":"string"},"spec":{"$ref":"#/components/schemas/IntervalVariableSpec"}},"required":["kind","spec"],"type":"object"},"IntervalVariableSpec":{"additionalProperties":false,"description":"Interval variable specification","properties":{"auto":{"default":false,"type":"boolean"},"auto_count":{"default":0,"type":"integer"},"auto_min":{"default":"","type":"string"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"refresh":{"const":"onTimeRangeChanged","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","auto","auto_min","auto_count","refresh","hide","skipUrlSync"],"type":"object"},"LibraryPanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"LibraryPanel","type":"string"},"spec":{"$ref":"#/components/schemas/LibraryPanelKindSpec"}},"required":["kind","spec"],"type":"object"},"LibraryPanelKindSpec":{"additionalProperties":false,"properties":{"id":{"description":"Panel ID for the library panel in the dashboard","type":"number"},"libraryPanel":{"$ref":"#/components/schemas/LibraryPanelRef"},"title":{"description":"Title for the library panel in the dashboard","type":"string"}},"required":["id","title","libraryPanel"],"type":"object"},"LibraryPanelRef":{"additionalProperties":false,"description":"A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.","properties":{"name":{"description":"Library panel name","type":"string"},"uid":{"description":"Library panel uid","type":"string"}},"required":["name","uid"],"type":"object"},"MappingType":{"description":"Supported value mapping types\n` + "`" + `value` + "`" + `: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n` + "`" + `range` + "`" + `: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n` + "`" + `regex` + "`" + `: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n` + "`" + `special` + "`" + `: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.","enum":["value","range","regex","special"],"type":"string"},"MatcherConfig":{"additionalProperties":false,"description":"Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.","properties":{"id":{"default":"","description":"The matcher id. This is used to find the matcher implementation from registry.","type":"string"},"options":{"additionalProperties":{},"description":"The matcher options. This is specific to the matcher implementation.","type":"object"}},"required":["id"],"type":"object"},"MetricFindValue":{"additionalProperties":false,"description":"Define the MetricFindValue type","properties":{"expandable":{"type":"boolean"},"group":{"type":"string"},"text":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]}},"required":["text"],"type":"object"},"NullValueMode":{"description":"How null values should be handled","enum":["null","connected","null as zero"],"type":"string"},"PanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"Panel","type":"string"},"spec":{"$ref":"#/components/schemas/PanelSpec"}},"required":["kind","spec"],"type":"object"},"PanelQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"PanelQuery","type":"string"},"spec":{"$ref":"#/components/schemas/PanelQuerySpec"}},"required":["kind","spec"],"type":"object"},"PanelQuerySpec":{"additionalProperties":false,"properties":{"hidden":{"type":"boolean"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refId":{"default":"A","type":"string"}},"required":["query","refId","hidden"],"type":"object"},"PanelSpec":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/QueryGroupKind"},"description":{"type":"string"},"id":{"type":"number"},"links":{"items":{"$ref":"#/components/schemas/DataLink"},"type":"array"},"title":{"type":"string"},"transparent":{"type":"boolean"},"vizConfig":{"$ref":"#/components/schemas/VizConfigKind"}},"required":["id","title","description","links","data","vizConfig"],"type":"object"},"QueryGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"QueryGroup","type":"string"},"spec":{"$ref":"#/components/schemas/QueryGroupSpec"}},"required":["kind","spec"],"type":"object"},"QueryGroupSpec":{"additionalProperties":false,"properties":{"queries":{"items":{"$ref":"#/components/schemas/PanelQueryKind"},"type":"array"},"queryOptions":{"$ref":"#/components/schemas/QueryOptionsSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationKind"},"type":"array"}},"required":["queries","transformations","queryOptions"],"type":"object"},"QueryOptionsSpec":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"string"},"hideTimeOverride":{"type":"boolean"},"interval":{"type":"string"},"maxDataPoints":{"type":"integer"},"queryCachingTTL":{"type":"integer"},"timeCompare":{"type":"string"},"timeFrom":{"type":"string"},"timeShift":{"type":"string"}},"type":"object"},"QueryVariableKind":{"additionalProperties":false,"description":"Query variable kind","properties":{"kind":{"const":"QueryVariable","type":"string"},"spec":{"$ref":"#/components/schemas/QueryVariableSpec"}},"required":["kind","spec"],"type":"object"},"QueryVariableSpec":{"additionalProperties":false,"description":"Query variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"definition":{"type":"string"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"placeholder":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"regexApplyTo":{"$ref":"#/components/schemas/VariableRegexApplyTo","default":"value"},"skipUrlSync":{"default":false,"type":"boolean"},"sort":{"$ref":"#/components/schemas/VariableSort"},"staticOptions":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"staticOptionsOrder":{"enum":["before","after","sorted"],"type":"string"}},"required":["name","current","hide","refresh","skipUrlSync","query","regex","sort","options","multi","includeAll","allowCustomValue"],"type":"object"},"RangeMap":{"additionalProperties":false,"description":"Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.","properties":{"options":{"additionalProperties":false,"description":"Range to match against and the result to apply when the value is within the range","properties":{"from":{"description":"Min value of the range. It can be null which means -Infinity","type":"number"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value is within the range"},"to":{"description":"Max value of the range. It can be null which means +Infinity","type":"number"}},"required":["from","to","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RegexMap":{"additionalProperties":false,"description":"Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.","properties":{"options":{"additionalProperties":false,"description":"Regular expression to match against and the result to apply when the value matches the regex","properties":{"pattern":{"description":"Regular expression to match against","type":"string"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the regex"}},"required":["pattern","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RepeatMode":{"const":"variable","description":"other repeat modes will be added in the future: label, frame","type":"string"},"RepeatOptions":{"additionalProperties":false,"properties":{"direction":{"enum":["h","v"],"type":"string"},"maxPerRow":{"type":"integer"},"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayoutRow","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutRowSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowSpec":{"additionalProperties":false,"properties":{"collapse":{"type":"boolean"},"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"fillScreen":{"type":"boolean"},"hideHeader":{"type":"boolean"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/RowRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"RowsLayoutSpec":{"additionalProperties":false,"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowsLayoutRowKind"},"type":"array"}},"required":["rows"],"type":"object"},"SpecialValueMap":{"additionalProperties":false,"description":"Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.","properties":{"options":{"additionalProperties":false,"properties":{"match":{"$ref":"#/components/schemas/SpecialValueMatch","description":"Special value to match against"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the special value"}},"required":["match","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"SpecialValueMatch":{"description":"Special value types supported by the ` + "`" + `SpecialValueMap` + "`" + `","enum":["true","false","null","nan","null+nan","empty"],"type":"string"},"SwitchVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"SwitchVariable","type":"string"},"spec":{"$ref":"#/components/schemas/SwitchVariableSpec"}},"required":["kind","spec"],"type":"object"},"SwitchVariableSpec":{"additionalProperties":false,"properties":{"current":{"default":"false","type":"string"},"description":{"type":"string"},"disabledValue":{"default":"false","type":"string"},"enabledValue":{"default":"true","type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","enabledValue","disabledValue","hide","skipUrlSync"],"type":"object"},"TabRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"TabsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutSpec":{"additionalProperties":false,"properties":{"tabs":{"items":{"$ref":"#/components/schemas/TabsLayoutTabKind"},"type":"array"}},"required":["tabs"],"type":"object"},"TabsLayoutTabKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayoutTab","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutTabSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutTabSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/TabRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"TextVariableKind":{"additionalProperties":false,"description":"Text variable kind","properties":{"kind":{"const":"TextVariable","type":"string"},"spec":{"$ref":"#/components/schemas/TextVariableSpec"}},"required":["kind","spec"],"type":"object"},"TextVariableSpec":{"additionalProperties":false,"description":"Text variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","query","hide","skipUrlSync"],"type":"object"},"Threshold":{"additionalProperties":false,"properties":{"color":{"type":"string"},"value":{"description":"Value null means -Infinity","type":"number"}},"required":["value","color"],"type":"object"},"ThresholdsConfig":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ThresholdsMode"},"steps":{"items":{"$ref":"#/components/schemas/Threshold"},"type":"array"}},"required":["mode","steps"],"type":"object"},"ThresholdsMode":{"enum":["absolute","percentage"],"type":"string"},"TimeRangeOption":{"additionalProperties":false,"properties":{"display":{"default":"Last 6 hours","type":"string"},"from":{"default":"now-6h","type":"string"},"to":{"default":"now","type":"string"}},"required":["display","from","to"],"type":"object"},"TimeSettingsSpec":{"additionalProperties":false,"description":"Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.","properties":{"autoRefresh":{"default":"","description":"Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh","type":"string"},"autoRefreshIntervals":{"default":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"description":"Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals","items":{"type":"string"},"type":"array"},"fiscalYearStartMonth":{"default":0,"description":"The month that the fiscal year starts on. 0 = January, 11 = December","type":"integer"},"from":{"default":"now-6h","description":"Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"hideTimepicker":{"default":false,"description":"Whether timepicker is visible or not.\nv1: timepicker.hidden","type":"boolean"},"nowDelay":{"description":"Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay","type":"string"},"quickRanges":{"description":"Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI","items":{"$ref":"#/components/schemas/TimeRangeOption"},"type":"array"},"timezone":{"default":"browser","description":"Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".","type":"string"},"to":{"default":"now","description":"End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"weekStart":{"description":"Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".","enum":["saturday","monday","sunday"],"type":"string"}},"required":["from","to","autoRefresh","autoRefreshIntervals","hideTimepicker","fiscalYearStartMonth"],"type":"object"},"TransformationKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a TransformationKind is the transformation ID","type":"string"},"spec":{"$ref":"#/components/schemas/DataTransformerConfig"}},"required":["kind","spec"],"type":"object"},"ValueMap":{"additionalProperties":false,"description":"Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.","properties":{"options":{"additionalProperties":{"$ref":"#/components/schemas/ValueMappingResult"},"description":"Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }","type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"ValueMapping":{"oneOf":[{"$ref":"#/components/schemas/ValueMap"},{"$ref":"#/components/schemas/RangeMap"},{"$ref":"#/components/schemas/RegexMap"},{"$ref":"#/components/schemas/SpecialValueMap"}]},"ValueMappingResult":{"additionalProperties":false,"description":"Result used as replacement with text and color when the value matches","properties":{"color":{"description":"Text to use when the value matches","type":"string"},"icon":{"description":"Icon to display when the value matches. Only specific visualizations.","type":"string"},"index":{"description":"Position in the mapping array. Only used internally.","type":"integer"},"text":{"description":"Text to display when the value matches","type":"string"}},"type":"object"},"VariableHide":{"description":"Determine if the variable shows on dashboard\nAccepted values are ` + "`" + `dontHide` + "`" + ` (show label and value), ` + "`" + `hideLabel` + "`" + ` (show value only), ` + "`" + `hideVariable` + "`" + ` (show nothing), ` + "`" + `inControlsMenu` + "`" + ` (show in a drop-down menu).","enum":["dontHide","hideLabel","hideVariable","inControlsMenu"],"type":"string"},"VariableKind":{"oneOf":[{"$ref":"#/components/schemas/QueryVariableKind"},{"$ref":"#/components/schemas/TextVariableKind"},{"$ref":"#/components/schemas/ConstantVariableKind"},{"$ref":"#/components/schemas/DatasourceVariableKind"},{"$ref":"#/components/schemas/IntervalVariableKind"},{"$ref":"#/components/schemas/CustomVariableKind"},{"$ref":"#/components/schemas/GroupByVariableKind"},{"$ref":"#/components/schemas/AdhocVariableKind"},{"$ref":"#/components/schemas/SwitchVariableKind"}]},"VariableOption":{"additionalProperties":false,"description":"Variable option specification","properties":{"selected":{"description":"Whether the option is selected or not","type":"boolean"},"text":{"description":"Text to be displayed for the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"value":{"description":"Value of the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]}},"required":["text","value"],"type":"object"},"VariableRefresh":{"description":"Options to config when to refresh a variable\n` + "`" + `never` + "`" + `: Never refresh the variable\n` + "`" + `onDashboardLoad` + "`" + `: Queries the data source every time the dashboard loads.\n` + "`" + `onTimeRangeChanged` + "`" + `: Queries the data source when the dashboard time range changes.","enum":["never","onDashboardLoad","onTimeRangeChanged"],"type":"string"},"VariableRegexApplyTo":{"description":"Determine whether regex applies to variable value or display text\nAccepted values are ` + "`" + `value` + "`" + ` (apply to value used in queries) or ` + "`" + `text` + "`" + ` (apply to display text shown to users)","enum":["value","text"],"type":"string"},"VariableSort":{"description":"Sort variable options\nAccepted values are:\n` + "`" + `disabled` + "`" + `: No sorting\n` + "`" + `alphabeticalAsc` + "`" + `: Alphabetical ASC\n` + "`" + `alphabeticalDesc` + "`" + `: Alphabetical DESC\n` + "`" + `numericalAsc` + "`" + `: Numerical ASC\n` + "`" + `numericalDesc` + "`" + `: Numerical DESC\n` + "`" + `alphabeticalCaseInsensitiveAsc` + "`" + `: Alphabetical Case Insensitive ASC\n` + "`" + `alphabeticalCaseInsensitiveDesc` + "`" + `: Alphabetical Case Insensitive DESC\n` + "`" + `naturalAsc` + "`" + `: Natural ASC\n` + "`" + `naturalDesc` + "`" + `: Natural DESC\nVariableSort enum with default value","enum":["disabled","alphabeticalAsc","alphabeticalDesc","numericalAsc","numericalDesc","alphabeticalCaseInsensitiveAsc","alphabeticalCaseInsensitiveDesc","naturalAsc","naturalDesc"],"type":"string"},"VizConfigKind":{"additionalProperties":false,"properties":{"group":{"description":"The group is the plugin ID","type":"string"},"kind":{"const":"VizConfig","type":"string"},"spec":{"$ref":"#/components/schemas/VizConfigSpec"},"version":{"type":"string"}},"required":["kind","group","version","spec"],"type":"object"},"VizConfigSpec":{"additionalProperties":false,"description":"--- Kinds ---","properties":{"fieldConfig":{"$ref":"#/components/schemas/FieldConfigSource"},"options":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"}},"required":["options","fieldConfig"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationQueryKind"},"type":"array"},"cursorSync":{"$ref":"#/components/schemas/DashboardCursorSync","default":"Off","description":"Configuration of dashboard cursor sync behavior.\n\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip."},"description":{"description":"Description of dashboard.","type":"string"},"editable":{"default":true,"description":"Whether a dashboard is editable or not.","type":"boolean"},"elements":{"additionalProperties":{"$ref":"#/components/schemas/Element"},"type":"object"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"links":{"description":"Links with references to other dashboards or external websites.","items":{"$ref":"#/components/schemas/DashboardLink"},"type":"array"},"liveNow":{"description":"When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.","type":"boolean"},"preload":{"default":false,"description":"When set to true, the dashboard will load all panels in the dashboard when it's loaded.","type":"boolean"},"revision":{"description":"Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.","type":"integer"},"tags":{"description":"Tags associated with dashboard.","items":{"type":"string"},"type":"array"},"timeSettings":{"$ref":"#/components/schemas/TimeSettingsSpec"},"title":{"description":"Title of dashboard.","type":"string"},"variables":{"description":"Configured template variables.","items":{"$ref":"#/components/schemas/VariableKind"},"type":"array"}},"required":["annotations","cursorSync","elements","layout","links","preload","tags","timeSettings","title","variables"],"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`)
versionSchemaDashboardv2beta1 app.VersionSchema
_ = json.Unmarshal(rawSchemaDashboardv2beta1, &versionSchemaDashboardv2beta1)
)
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v1beta1.annotation-filtering.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v1beta1.annotation-filtering.v42.json
new file mode 100644
index 00000000000..5e3d12546e1
--- /dev/null
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v1beta1.annotation-filtering.v42.json
@@ -0,0 +1,427 @@
+{
+ "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,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations \u0026 Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ },
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "enable": true,
+ "filter": {
+ "exclude": false,
+ "ids": [
+ 1
+ ]
+ },
+ "iconColor": "red",
+ "name": "Red, only panel 1",
+ "target": {
+ "lines": 4,
+ "refId": "Anno",
+ "scenarioId": "annotations"
+ }
+ },
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "enable": true,
+ "filter": {
+ "exclude": true,
+ "ids": [
+ 1
+ ]
+ },
+ "iconColor": "yellow",
+ "name": "Yellow - all except 1",
+ "target": {
+ "lines": 5,
+ "refId": "Anno",
+ "scenarioId": "annotations"
+ }
+ },
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "enable": true,
+ "filter": {
+ "exclude": false,
+ "ids": [
+ 3,
+ 4
+ ]
+ },
+ "iconColor": "dark-purple",
+ "name": "Purple only panel 3+4",
+ "target": {
+ "lines": 6,
+ "refId": "Anno",
+ "scenarioId": "annotations"
+ }
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": 119,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "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
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 0
+ },
+ "id": 1,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "title": "Panel one",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "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
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "title": "Panel two",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "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
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 8
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "title": "Panel three",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "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
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 8
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "title": "Panel four",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 42,
+ "tags": [
+ "gdev",
+ "annotations"
+ ],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-30m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "Annotation filtering",
+ "uid": "ed155665",
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v1beta1.multi-lane-annotations.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/annotations/v1beta1.multi-lane-annotations.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v1beta1.elasticsearch_complex.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v1beta1.elasticsearch_complex.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v1beta1.elasticsearch_migration.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v1beta1.elasticsearch_migration.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v1beta1.elasticsearch_simple.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-elasticsearch/v1beta1.elasticsearch_simple.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v1beta1.influxdb-logs.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v1beta1.influxdb-logs.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v1beta1.influxdb-templated.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-influxdb/v1beta1.influxdb-templated.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v1beta1.loki_fakedata.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v1beta1.loki_fakedata.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v1beta1.loki_query_splitting.v42.json
similarity index 98%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v1beta1.loki_query_splitting.v42.json
index a7beffa4cdc..8af239195cb 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-loki/v1beta1.loki_query_splitting.v42.json
@@ -219,8 +219,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -312,8 +311,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -492,8 +490,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -584,8 +581,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -676,8 +672,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -791,8 +786,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -906,8 +900,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -1022,8 +1015,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v1beta1.mssql_fakedata.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v1beta1.mssql_fakedata.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v1beta1.mssql_unittest.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mssql/v1beta1.mssql_unittest.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v1beta1.mysql_fakedata.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v1beta1.mysql_fakedata.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v1beta1.mysql_unittest.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-mysql/v1beta1.mysql_unittest.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v1beta1.opentsdb.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v1beta1.opentsdb.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v1beta1.opentsdb_v23.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-opentsdb/v1beta1.opentsdb_v23.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v1beta1.postgres_fakedata.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v1beta1.postgres_fakedata.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v1beta1.postgres_unittest.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-postgres/v1beta1.postgres_unittest.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.bar-gauge-demo2.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.bar-gauge-demo2.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.demo1.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.demo1.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.new_features_in_v74.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.new_features_in_v74.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.new_features_in_v8.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/datasource-testdata/v1beta1.new_features_in_v8.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-Kitchen-Sink.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-Kitchen-Sink.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-panel-horizontally.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-panel-horizontally.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-panel-vertically.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-panel-vertically.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-row-with-a-repeating-vertical-panel.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-a-row-with-a-repeating-vertical-panel.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-an-empty-row.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/e2e-repeats/v1beta1.Repeating-an-empty-row.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v1beta1.link-onclick-extensions.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v1beta1.link-onclick-extensions.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v1beta1.link-path-extensions.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/extensions/v1beta1.link-path-extensions.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.datadata-macros.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.datadata-macros.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.global-variables-and-interpolation.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.global-variables-and-interpolation.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-dashboard-links-and-variables.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-dashboard-links-and-variables.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-repeating-panels.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-repeating-panels.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-repeating-rows.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-repeating-rows.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-textbox-e2e-scenarios.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.templating-textbox-e2e-scenarios.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-datalinks.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-datalinks.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-nested-variables-drilldown.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-nested-variables-drilldown.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-nested-variables.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-nested-variables.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-test-variable-output.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-test-variable-output.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-variables-textbox.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-variables-textbox.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-variables-that-update-on-time-change.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/feature-templating/v1beta1.testdata-variables-that-update-on-time-change.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-flakey-refresh.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-flakey-refresh.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-flakey.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-flakey.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-publish.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-publish.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-streams.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/live/v1beta1.live-streams.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v1beta1.migrations.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v1beta1.migrations.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-autosizing.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-autosizing.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-label-rotation-skipping.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-label-rotation-skipping.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-series-toggle.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-series-toggle.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-thresholds-mappings.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-thresholds-mappings.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-tooltips-legends.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-barchart/v1beta1.barchart-tooltips-legends.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v1beta1.bar_gauge_demo.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v1beta1.bar_gauge_demo.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v1beta1.panel_tests_bar_gauge.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v1beta1.panel_tests_bar_gauge.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v1beta1.panel_tests_bar_gauge2.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-bargauge/v1beta1.panel_tests_bar_gauge2.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-candlestick/v1beta1.candlestick.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-candlestick/v1beta1.candlestick.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v1beta1.canvas-connection-examples.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v1beta1.canvas-connection-examples.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v1beta1.canvas-datalinks.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v1beta1.canvas-datalinks.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v1beta1.canvas-examples.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-canvas/v1beta1.canvas-examples.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.auto_decimals.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.auto_decimals.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.color_modes.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.color_modes.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.lazy_loading.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.lazy_loading.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.linked-viz.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.linked-viz.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.panels_without_title.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.panels_without_title.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.shared_queries.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-common/v1beta1.shared_queries.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-dashlist/v1beta1.dashlist.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-dashlist/v1beta1.dashlist.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-datagrid/v1beta1.datagrid_metric_values.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-datagrid/v1beta1.datagrid_metric_values.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-flamegraph/v1beta1.panel_tests_flame_graph.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-flamegraph/v1beta1.panel_tests_flame_graph.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge-multi-series.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge-multi-series.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests.v42.json
similarity index 93%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests.v42.json
index 92a865b0b10..b00b08dd2ab 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests.v42.json
@@ -65,17 +65,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -136,17 +133,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -207,17 +201,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -271,7 +262,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -279,17 +269,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -342,7 +329,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -350,17 +336,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -414,7 +397,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -422,17 +404,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -485,7 +464,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -493,17 +471,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -668,7 +643,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -685,17 +659,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
- "color": "#e24d42",
- "index": 2,
+ "color": "#e24d42",
"value": 90
}
]
@@ -750,7 +721,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -768,17 +738,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -833,7 +800,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -852,17 +818,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -917,7 +880,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -946,17 +908,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -1038,7 +997,7 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "2",
+ "decimals": 2,
"mappings": [],
"max": 100,
"min": 0,
@@ -1046,17 +1005,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests_new.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests_new.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests_old_to_new.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v1beta1.gauge_tests_old_to_new.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-color-field.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-color-field.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-photo-layer.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-photo-layer.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-route-layer.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-route-layer.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-spatial-operations-transformer.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-spatial-operations-transformer.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-v91.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap-v91.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap_multi-layers.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.geomap_multi-layers.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.panel-geomap.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-geomap/v1beta1.panel-geomap.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph-gradient-area-fills.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph-gradient-area-fills.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph-shared-tooltips.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph-shared-tooltips.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph-time-regions.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph-time-regions.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph_tests.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph_tests.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph_y_axis.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-graph/v1beta1.graph_y_axis.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v1beta1.heatmap-calculate-log.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v1beta1.heatmap-calculate-log.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v1beta1.heatmap-legacy.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v1beta1.heatmap-legacy.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v1beta1.heatmap-x.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-heatmap/v1beta1.heatmap-x.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-histogram/v1beta1.histogram_tests.v42.json
similarity index 99%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-histogram/v1beta1.histogram_tests.v42.json
index 7e392bd55d0..5b3e0ed0b72 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-histogram/v1beta1.histogram_tests.v42.json
@@ -58,8 +58,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -127,8 +126,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -196,8 +194,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -277,8 +274,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -355,8 +351,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -448,8 +443,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -536,8 +530,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -619,8 +612,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -702,8 +694,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -785,8 +776,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -850,8 +840,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-library/v1beta1.panel-library.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-library/v1beta1.panel-library.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-piechart/v1beta1.panel_test_piechart.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-piechart/v1beta1.panel_test_piechart.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-polystat/v1beta1.polystat_test.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-polystat/v1beta1.polystat_test.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-stat/v1beta1.panel-stat-tests.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-stat/v1beta1.panel-stat-tests.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-status-history/v1beta1.status-history-thresholds-mappings.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-status-history/v1beta1.status-history-thresholds-mappings.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_footer.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_footer.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_kitchen_sink.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_kitchen_sink.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_markdown.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_markdown.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_pagination.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_pagination.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_sparkline_cell.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_sparkline_cell.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_tests.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_tests.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_tests_new.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_tests_new.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_v12_2_migrations.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-table/v1beta1.table_v12_2_migrations.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-text/v1beta1.text-options.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-text/v1beta1.text-options.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-align-endtime.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-align-endtime.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-align-nulls-retain.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-align-nulls-retain.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-demo.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-demo.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-modes.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-modes.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-thresholds-mappings.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeline/v1beta1.timeline-thresholds-mappings.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-bars-high-density.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-bars-high-density.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-by-value-color-schemes.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-by-value-color-schemes.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-formats.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-formats.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-gradient-area.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-gradient-area.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-hue-gradients.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-hue-gradients.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-nulls.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-nulls.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-out-of-rage.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-out-of-rage.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-shared-tooltip-cursor-position.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-shared-tooltip-cursor-position.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-soft-limits.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-soft-limits.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-stacking.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-stacking.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-stacking2.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-stacking2.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-thresholds.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-thresholds.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-time.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-time.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-y-ticks-zero-decimals.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-y-ticks-zero-decimals.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-yaxis-ticks.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries-yaxis-ticks.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-timeseries/v1beta1.timeseries.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-trend/v1beta1.trend_example.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-trend/v1beta1.trend_example.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-demo.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-demo.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-migrations.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-migrations.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-tooltip-color-test.v42.json
similarity index 98%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-tooltip-color-test.v42.json
index 417ea1661e1..f28fee864e5 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-xychart/v1beta1.xychart-tooltip-color-test.v42.json
@@ -61,8 +61,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -148,8 +147,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -235,8 +233,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -322,8 +319,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -416,8 +412,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -510,8 +505,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -604,8 +598,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.mostly-blank-dashboard.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.mostly-blank-dashboard.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.relative_time_zone_support.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.relative_time_zone_support.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.slow_queries_and_annotations.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.slow_queries_and_annotations.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.tall_dashboard.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.tall_dashboard.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.time_zone_support.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/scenarios/v1beta1.time_zone_support.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.config-from-query.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.config-from-query.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.extract-json-paths.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.extract-json-paths.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.filter.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.filter.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.join-by-field.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.join-by-field.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.join-by-labels.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.join-by-labels.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.regression-analysis.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.regression-analysis.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.reuse.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.reuse.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.rows-to-fields.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/transforms/v1beta1.rows-to-fields.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v1beta1.v1beta1.v1beta1.all-panels.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v1beta1.v1beta1.v1beta1.all-panels.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v1beta1.v1beta1.v1beta1.home.v42.json
similarity index 100%
rename from apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.json
rename to apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/v1beta1.v1beta1.v1beta1.home.v42.json
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v2alpha1.ds-data-query.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v2alpha1.ds-data-query.json
index a27c33c3239..ac88b83370f 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/input/v2alpha1.ds-data-query.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v2alpha1.ds-data-query.json
@@ -852,6 +852,194 @@
}
}
}
+ },
+ "panel-7": {
+ "kind": "Panel",
+ "spec": {
+ "id": 7,
+ "title": "Single Dashboard DS Query",
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "stat",
+ "spec": {
+ "pluginVersion": "12.1.0-pre",
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
+ },
+ "panel-8": {
+ "kind": "Panel",
+ "spec": {
+ "id": 8,
+ "title": "Multiple Dashboard DS Queries",
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 2,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "B",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 3,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "C",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "stat",
+ "spec": {
+ "pluginVersion": "12.1.0-pre",
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
}
},
"layout": {
@@ -914,6 +1102,24 @@
"name": "panel-6"
}
}
+ },
+ {
+ "kind": "AutoGridLayoutItem",
+ "spec": {
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-7"
+ }
+ }
+ },
+ {
+ "kind": "AutoGridLayoutItem",
+ "spec": {
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-8"
+ }
+ }
}
]
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.ds-data-query.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.ds-data-query.json
index 0c787609714..fad72787d19 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.ds-data-query.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.ds-data-query.json
@@ -879,6 +879,200 @@
}
}
}
+ },
+ "panel-7": {
+ "kind": "Panel",
+ "spec": {
+ "id": 7,
+ "title": "Single Dashboard DS Query",
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "VizConfig",
+ "group": "stat",
+ "version": "12.1.0-pre",
+ "spec": {
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
+ },
+ "panel-8": {
+ "kind": "Panel",
+ "spec": {
+ "id": 8,
+ "title": "Multiple Dashboard DS Queries",
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 2,
+ "withTransforms": true
+ }
+ },
+ "refId": "B",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 3,
+ "withTransforms": true
+ }
+ },
+ "refId": "C",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "VizConfig",
+ "group": "stat",
+ "version": "12.1.0-pre",
+ "spec": {
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
}
},
"layout": {
@@ -973,6 +1167,32 @@
"name": "panel-6"
}
}
+ },
+ {
+ "kind": "GridLayoutItem",
+ "spec": {
+ "x": 0,
+ "y": 6,
+ "width": 8,
+ "height": 3,
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-7"
+ }
+ }
+ },
+ {
+ "kind": "GridLayoutItem",
+ "spec": {
+ "x": 8,
+ "y": 6,
+ "width": 8,
+ "height": 3,
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-8"
+ }
+ }
}
]
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json
index b6addcc81ed..d6f207c6fdd 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json
@@ -970,8 +970,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
@@ -1064,8 +1063,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json
index f806e27a98f..c7ef28fa2b8 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json
@@ -991,8 +991,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
@@ -1087,8 +1086,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v1beta1.json
index aed8292522f..3cb966ba891 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v1beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v1beta1.json
@@ -225,8 +225,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -318,8 +317,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -498,8 +496,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -590,8 +587,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -682,8 +678,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -797,8 +792,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -912,8 +906,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -1028,8 +1021,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json
index b1dbd3de041..4d208a1d8dc 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json
@@ -467,7 +467,8 @@
"title": "Go to drilldown",
"url": "/d/O6GmNPvWk/dashboard-tests-nested-template-variables-drilldown?orgId=1\u0026${__all_variables}\u0026${__url_time_range}"
}
- ]
+ ],
+ "nullValueMode": "connected"
},
"overrides": []
}
@@ -550,7 +551,8 @@
"title": "Go to drilldown",
"url": "/d/O6GmNPvWk/dashboard-tests-nested-template-variables-drilldown?orgId=1\u0026${__all_variables}\u0026${__url_time_range}"
}
- ]
+ ],
+ "nullValueMode": "connected"
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json
index 9089dd1d1fb..5165f97554d 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json
@@ -481,7 +481,8 @@
"title": "Go to drilldown",
"url": "/d/O6GmNPvWk/dashboard-tests-nested-template-variables-drilldown?orgId=1\u0026${__all_variables}\u0026${__url_time_range}"
}
- ]
+ ],
+ "nullValueMode": "connected"
},
"overrides": []
}
@@ -566,7 +567,8 @@
"title": "Go to drilldown",
"url": "/d/O6GmNPvWk/dashboard-tests-nested-template-variables-drilldown?orgId=1\u0026${__all_variables}\u0026${__url_time_range}"
}
- ]
+ ],
+ "nullValueMode": "connected"
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json
index 2eb67e36f2f..a1b9c4b230a 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json
@@ -169,8 +169,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
@@ -336,8 +335,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
@@ -408,8 +406,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json
index acba4cedbc2..2428d6fd107 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json
@@ -175,8 +175,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
@@ -347,8 +346,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
@@ -420,8 +418,7 @@
"le": 1e-9
},
"legend": {
- "show": true,
- "showLegend": true
+ "show": true
},
"rowsFrame": {
"layout": "auto"
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v1beta1.json
index b65608bc758..c3435f0f17d 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v1beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v1beta1.json
@@ -64,8 +64,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -133,8 +132,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -202,8 +200,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -283,8 +280,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -361,8 +357,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -454,8 +449,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -542,8 +536,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -625,8 +618,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -708,8 +700,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -791,8 +782,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -856,8 +846,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json
index 57fbcad9d99..ec5c52a7119 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json
@@ -882,6 +882,7 @@
"kind": "filterFieldsByName",
"spec": {
"id": "filterFieldsByName",
+ "disabled": true,
"options": {
"include": {
"names": [
@@ -895,6 +896,7 @@
"kind": "histogram",
"spec": {
"id": "histogram",
+ "disabled": true,
"options": {
"combine": true,
"fields": {}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json
index 5b2ee8d8df2..3ff41469dbc 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json
@@ -911,6 +911,7 @@
"kind": "filterFieldsByName",
"spec": {
"id": "filterFieldsByName",
+ "disabled": true,
"options": {
"include": {
"names": [
@@ -924,6 +925,7 @@
"kind": "histogram",
"spec": {
"id": "histogram",
+ "disabled": true,
"options": {
"combine": true,
"fields": {}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json
index d90c8dc52cd..b9ba0b13da4 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json
@@ -222,7 +222,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -318,7 +319,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -424,7 +426,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -507,7 +510,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json
index 7aaa0fff33a..e130fc7e172 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json
@@ -229,7 +229,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -328,7 +329,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -437,7 +439,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -523,7 +526,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json
index 540f0d9e54d..9d15475c82d 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json
@@ -167,7 +167,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -361,7 +362,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -596,7 +598,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -787,7 +790,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -974,7 +978,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1181,7 +1186,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1384,7 +1390,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1573,7 +1580,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json
index 6c9aa023163..3965312c00a 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json
@@ -173,7 +173,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -372,7 +373,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -612,7 +614,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -808,7 +811,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1000,7 +1004,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1212,7 +1217,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1420,7 +1426,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1614,7 +1621,8 @@
},
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json
index bccce10d162..f342cab8373 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json
@@ -194,7 +194,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1190,7 +1191,8 @@
"reducer": []
},
"inspect": true
- }
+ },
+ "fieldMinMax": true
},
"overrides": []
}
@@ -1262,7 +1264,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1418,7 +1421,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1575,7 +1579,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1737,7 +1742,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1888,7 +1894,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json
index 5e186ef1443..59f9b3d7942 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json
@@ -200,7 +200,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1208,7 +1209,8 @@
"reducer": []
},
"inspect": true
- }
+ },
+ "fieldMinMax": true
},
"overrides": []
}
@@ -1283,7 +1285,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1442,7 +1445,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1602,7 +1606,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1767,7 +1772,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1921,7 +1927,8 @@
"type": "auto"
},
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json
index e5e260fd150..92729fdddcb 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json
@@ -302,6 +302,23 @@
"url": "https://google.com/search?q=grafana"
}
],
+ "actions": [
+ {
+ "type": "fetch",
+ "title": "Get instance health",
+ "fetch": {
+ "method": "GET",
+ "url": "/api/health",
+ "body": "{}",
+ "headers": [
+ [
+ "Content-Type",
+ "application/json"
+ ]
+ ]
+ }
+ }
+ ],
"custom": {
"align": "auto",
"cellOptions": {
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json
index ac15a298939..5246af0a06b 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json
@@ -312,6 +312,23 @@
"url": "https://google.com/search?q=grafana"
}
],
+ "actions": [
+ {
+ "type": "fetch",
+ "title": "Get instance health",
+ "fetch": {
+ "method": "GET",
+ "url": "/api/health",
+ "body": "{}",
+ "headers": [
+ [
+ "Content-Type",
+ "application/json"
+ ]
+ ]
+ }
+ }
+ ],
"custom": {
"align": "auto",
"cellOptions": {
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json
index 1b6348c35d5..d6451b5d80f 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json
@@ -206,7 +206,8 @@
},
"filterable": true,
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -753,7 +754,8 @@
},
"filterable": true,
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1303,7 +1305,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1497,7 +1500,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1692,7 +1696,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1886,7 +1891,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -2081,7 +2087,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -2276,7 +2283,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json
index a77140c5beb..75353a995ac 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json
@@ -212,7 +212,8 @@
},
"filterable": true,
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -764,7 +765,8 @@
},
"filterable": true,
"inspect": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1319,7 +1321,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1518,7 +1521,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1718,7 +1722,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -1917,7 +1922,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -2117,7 +2123,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
@@ -2317,7 +2324,8 @@
"filterable": true,
"inspect": false,
"wrapText": false
- }
+ },
+ "fieldMinMax": true
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json
index 8dff3c34ccf..3366490c00a 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json
@@ -222,7 +222,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -318,7 +319,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -424,7 +426,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -507,7 +510,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json
index 3baa3d21130..59d2e929972 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json
@@ -229,7 +229,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -328,7 +329,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -437,7 +439,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -523,7 +526,8 @@
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json
index 5b43876c65f..bde73320d42 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json
@@ -110,7 +110,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -217,7 +218,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -324,7 +326,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -431,7 +434,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -537,7 +541,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -643,7 +648,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json
index ca96f9d5720..06331f32233 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json
@@ -114,7 +114,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -223,7 +224,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -332,7 +334,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -441,7 +444,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -549,7 +553,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -657,7 +662,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json
index 74cba148009..c4bb5720d36 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json
@@ -116,7 +116,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -229,7 +230,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -342,7 +344,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -455,7 +458,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -568,7 +572,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -681,7 +686,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -794,7 +800,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -907,7 +914,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -1020,7 +1028,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json
index 7e64bc79ef3..7c18be27f07 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json
@@ -120,7 +120,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -235,7 +236,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -350,7 +352,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -465,7 +468,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -580,7 +584,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -695,7 +700,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -810,7 +816,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -925,7 +932,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -1040,7 +1048,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json
index e50b453076a..cb63e4f234d 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json
@@ -3607,7 +3607,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -3740,7 +3741,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json
index 65105663c85..a57e430cc63 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json
@@ -3674,7 +3674,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
@@ -3809,7 +3810,8 @@
},
"showPoints": "never",
"spanNulls": true
- }
+ },
+ "nullValueMode": "null"
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v1beta1.json
index cdc93bf3cfa..1b7c9effbe8 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v1beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v1beta1.json
@@ -67,8 +67,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -154,8 +153,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -241,8 +239,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -328,8 +325,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -422,8 +418,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -516,8 +511,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -610,8 +604,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json
index 1d60f0ef9bf..861c4b41a6c 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json
@@ -124,7 +124,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -225,7 +226,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -326,7 +328,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -434,7 +437,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -542,7 +546,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -650,7 +655,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json
index 5a46646474d..0ae6dc172bd 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json
@@ -128,7 +128,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -232,7 +233,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -336,7 +338,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -447,7 +450,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -558,7 +562,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
@@ -669,7 +674,8 @@
"type": "linear"
},
"show": "points"
- }
+ },
+ "fieldMinMax": false
},
"overrides": []
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json
index 056fdc62383..dd3ba7146e5 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json
@@ -81,6 +81,10 @@
"kind": "reduce",
"spec": {
"id": "reduce",
+ "filter": {
+ "id": "byRefId",
+ "options": "A"
+ },
"options": {
"includeTimeField": false,
"mode": "reduceFields",
@@ -94,6 +98,10 @@
"kind": "reduce",
"spec": {
"id": "reduce",
+ "filter": {
+ "id": "byRefId",
+ "options": "B"
+ },
"options": {
"includeTimeField": false,
"mode": "reduceFields",
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json
index 57c5559add1..0f4ab69c96a 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json
@@ -86,6 +86,10 @@
"kind": "reduce",
"spec": {
"id": "reduce",
+ "filter": {
+ "id": "byRefId",
+ "options": "A"
+ },
"options": {
"includeTimeField": false,
"mode": "reduceFields",
@@ -99,6 +103,10 @@
"kind": "reduce",
"spec": {
"id": "reduce",
+ "filter": {
+ "id": "byRefId",
+ "options": "B"
+ },
"options": {
"includeTimeField": false,
"mode": "reduceFields",
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json
index 20f70a0a647..b38cf688949 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 18
+ },
+ "id": 7,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ }
+ ],
+ "title": "Single Dashboard DS Query",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "mixed",
+ "uid": "-- Mixed --"
+ },
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 18
+ },
+ "id": 8,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 2,
+ "refId": "B",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 3,
+ "refId": "C",
+ "withTransforms": true
+ }
+ ],
+ "title": "Multiple Dashboard DS Queries",
+ "type": "stat"
}
],
"preload": false,
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v1beta1.json
index 9956ad6962f..0b7f512e6f1 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v1beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v1beta1.json
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 18
+ },
+ "id": 7,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ }
+ ],
+ "title": "Single Dashboard DS Query",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "mixed",
+ "uid": "-- Mixed --"
+ },
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 18
+ },
+ "id": 8,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 2,
+ "refId": "B",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 3,
+ "refId": "C",
+ "withTransforms": true
+ }
+ ],
+ "title": "Multiple Dashboard DS Queries",
+ "type": "stat"
}
],
"preload": 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 09e35c64258..aba5db6146d 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
@@ -879,6 +879,200 @@
}
}
}
+ },
+ "panel-7": {
+ "kind": "Panel",
+ "spec": {
+ "id": 7,
+ "title": "Single Dashboard DS Query",
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "VizConfig",
+ "group": "stat",
+ "version": "12.1.0-pre",
+ "spec": {
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
+ },
+ "panel-8": {
+ "kind": "Panel",
+ "spec": {
+ "id": 8,
+ "title": "Multiple Dashboard DS Queries",
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 2,
+ "withTransforms": true
+ }
+ },
+ "refId": "B",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "DataQuery",
+ "group": "datasource",
+ "version": "v0",
+ "datasource": {
+ "name": "-- Dashboard --"
+ },
+ "spec": {
+ "panelId": 3,
+ "withTransforms": true
+ }
+ },
+ "refId": "C",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "VizConfig",
+ "group": "stat",
+ "version": "12.1.0-pre",
+ "spec": {
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
}
},
"layout": {
@@ -941,6 +1135,24 @@
"name": "panel-6"
}
}
+ },
+ {
+ "kind": "AutoGridLayoutItem",
+ "spec": {
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-7"
+ }
+ }
+ },
+ {
+ "kind": "AutoGridLayoutItem",
+ "spec": {
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-8"
+ }
+ }
}
]
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json
index 4494023eb13..99bcc3e9581 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 8,
+ "x": 0,
+ "y": 6
+ },
+ "id": 7,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ }
+ ],
+ "title": "Single Dashboard DS Query",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "mixed",
+ "uid": "-- Mixed --"
+ },
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 8,
+ "x": 8,
+ "y": 6
+ },
+ "id": 8,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 2,
+ "refId": "B",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 3,
+ "refId": "C",
+ "withTransforms": true
+ }
+ ],
+ "title": "Multiple Dashboard DS Queries",
+ "type": "stat"
}
],
"preload": false,
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v1beta1.json
index bc8d90d796a..e2d54185ea5 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v1beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v1beta1.json
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 8,
+ "x": 0,
+ "y": 6
+ },
+ "id": 7,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ }
+ ],
+ "title": "Single Dashboard DS Query",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "mixed",
+ "uid": "-- Mixed --"
+ },
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ }
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 8,
+ "x": 8,
+ "y": 6
+ },
+ "id": 8,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 1,
+ "refId": "A",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 2,
+ "refId": "B",
+ "withTransforms": true
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "panelId": 3,
+ "refId": "C",
+ "withTransforms": true
+ }
+ ],
+ "title": "Multiple Dashboard DS Queries",
+ "type": "stat"
}
],
"preload": false,
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v2alpha1.json
index bb70e99ec48..d3ca201e380 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v2alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v2alpha1.json
@@ -852,6 +852,194 @@
}
}
}
+ },
+ "panel-7": {
+ "kind": "Panel",
+ "spec": {
+ "id": 7,
+ "title": "Single Dashboard DS Query",
+ "description": "Panel with a single -- Dashboard -- datasource query",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "stat",
+ "spec": {
+ "pluginVersion": "12.1.0-pre",
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
+ },
+ "panel-8": {
+ "kind": "Panel",
+ "spec": {
+ "id": 8,
+ "title": "Multiple Dashboard DS Queries",
+ "description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
+ "links": [],
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 1,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "A",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 2,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "B",
+ "hidden": false
+ }
+ },
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "query": {
+ "kind": "datasource",
+ "spec": {
+ "panelId": 3,
+ "withTransforms": true
+ }
+ },
+ "datasource": {
+ "type": "datasource",
+ "uid": "-- Dashboard --"
+ },
+ "refId": "C",
+ "hidden": false
+ }
+ }
+ ],
+ "transformations": [],
+ "queryOptions": {}
+ }
+ },
+ "vizConfig": {
+ "kind": "stat",
+ "spec": {
+ "pluginVersion": "12.1.0-pre",
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "value": 0,
+ "color": "green"
+ }
+ ]
+ },
+ "color": {
+ "mode": "thresholds"
+ }
+ },
+ "overrides": []
+ }
+ }
+ }
+ }
}
},
"layout": {
@@ -946,6 +1134,32 @@
"name": "panel-6"
}
}
+ },
+ {
+ "kind": "GridLayoutItem",
+ "spec": {
+ "x": 0,
+ "y": 6,
+ "width": 8,
+ "height": 3,
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-7"
+ }
+ }
+ },
+ {
+ "kind": "GridLayoutItem",
+ "spec": {
+ "x": 8,
+ "y": 6,
+ "width": 8,
+ "height": 3,
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-8"
+ }
+ }
}
]
}
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json
index 716b476f825..40b4fff030e 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json
@@ -586,6 +586,7 @@
},
"id": -1,
"panels": [],
+ "repeat": "custom_var_tab",
"title": "Repeated Tab by \"$custom_var_tab\"",
"type": "row"
},
@@ -610,8 +611,11 @@
"y": 22
},
"id": 6,
+ "maxPerRow": 3,
"options": {},
"pluginVersion": "12.4.0-19736337744",
+ "repeat": "custom_var_panel",
+ "repeatDirection": "h",
"targets": [
{
"refId": "A"
diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v1beta1.json
index f915142fd14..ff1e2d42e20 100644
--- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v1beta1.json
+++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v1beta1.json
@@ -586,6 +586,7 @@
},
"id": -1,
"panels": [],
+ "repeat": "custom_var_tab",
"title": "Repeated Tab by \"$custom_var_tab\"",
"type": "row"
},
@@ -610,8 +611,11 @@
"y": 22
},
"id": 6,
+ "maxPerRow": 3,
"options": {},
"pluginVersion": "12.4.0-19736337744",
+ "repeat": "custom_var_panel",
+ "repeatDirection": "h",
"targets": [
{
"refId": "A"
diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go
index 3eeb61893fa..bfff3c49797 100644
--- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go
+++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go
@@ -2230,6 +2230,20 @@ func transformPanelTransformations(panelMap map[string]interface{}) []dashv2alph
Options: options,
},
}
+
+ // Extract disabled if present (optional, transformations are enabled by default)
+ if disabled, ok := tMap["disabled"].(bool); ok && disabled {
+ transformationKind.Spec.Disabled = &disabled
+ }
+
+ // Extract filter if present (optional frame matcher for transformations)
+ if filterMap, ok := tMap["filter"].(map[string]interface{}); ok {
+ transformationKind.Spec.Filter = &dashv2alpha1.DashboardMatcherConfig{
+ Id: schemaversion.GetStringValue(filterMap, "id"),
+ Options: filterMap["options"],
+ }
+ }
+
result = append(result, transformationKind)
}
}
@@ -2349,14 +2363,6 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo
}
}
- // 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
- }
-
// Handle Angular panel migrations
// This replicates the v0→v1 migration logic for panels that weren't migrated yet.
// We check two cases:
@@ -2531,6 +2537,15 @@ func extractFieldConfigDefaults(defaults map[string]interface{}) dashv2alpha1.Da
fieldConfigDefaults.Writeable = val
hasDefaults = true
}
+ if val, ok := extractBoolField(defaults, "fieldMinMax"); ok {
+ fieldConfigDefaults.FieldMinMax = val
+ hasDefaults = true
+ }
+ if val, ok := defaults["nullValueMode"].(string); ok {
+ nullValueMode := dashv2alpha1.DashboardNullValueMode(val)
+ fieldConfigDefaults.NullValueMode = &nullValueMode
+ hasDefaults = true
+ }
// Extract array field - strip BOMs from link URLs
if linksArray, ok := extractArrayField(defaults, "links"); ok {
@@ -2543,6 +2558,12 @@ func extractFieldConfigDefaults(defaults map[string]interface{}) dashv2alpha1.Da
hasDefaults = true
}
+ // Extract actions array
+ if actionsArray, ok := extractArrayField(defaults, "actions"); ok {
+ fieldConfigDefaults.Actions = convertActionsToV2(actionsArray)
+ hasDefaults = true
+ }
+
// Extract mappings
if mappings, exists := defaults["mappings"]; exists {
resultMappings := buildValueMappings(mappings)
@@ -2842,6 +2863,157 @@ func extractFieldConfigOverrides(fieldConfig map[string]interface{}) []dashv2alp
return result
}
+// convertActionsToV2 converts an array of V1 action objects to V2 DashboardAction structs.
+func convertActionsToV2(actionsArray []interface{}) []dashv2alpha1.DashboardAction {
+ if len(actionsArray) == 0 {
+ return nil
+ }
+
+ result := make([]dashv2alpha1.DashboardAction, 0, len(actionsArray))
+ for _, action := range actionsArray {
+ actionMap, ok := action.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ dashAction := dashv2alpha1.DashboardAction{
+ Type: dashv2alpha1.DashboardActionType(schemaversion.GetStringValue(actionMap, "type")),
+ Title: schemaversion.GetStringValue(actionMap, "title"),
+ }
+
+ // Convert confirmation
+ if confirmation, ok := actionMap["confirmation"].(string); ok && confirmation != "" {
+ dashAction.Confirmation = &confirmation
+ }
+
+ // Convert oneClick
+ if oneClick, ok := actionMap["oneClick"].(bool); ok {
+ dashAction.OneClick = &oneClick
+ }
+
+ // Convert fetch options
+ if fetchMap, ok := actionMap["fetch"].(map[string]interface{}); ok {
+ dashAction.Fetch = convertFetchOptionsToV2(fetchMap)
+ }
+
+ // Convert infinity options
+ if infinityMap, ok := actionMap["infinity"].(map[string]interface{}); ok {
+ dashAction.Infinity = convertInfinityOptionsToV2(infinityMap)
+ }
+
+ // Convert variables
+ if variablesArray, ok := actionMap["variables"].([]interface{}); ok {
+ dashAction.Variables = convertActionVariablesToV2(variablesArray)
+ }
+
+ // Convert style
+ if styleMap, ok := actionMap["style"].(map[string]interface{}); ok {
+ dashAction.Style = convertActionStyleToV2(styleMap)
+ }
+
+ result = append(result, dashAction)
+ }
+
+ return result
+}
+
+func convertFetchOptionsToV2(fetchMap map[string]interface{}) *dashv2alpha1.DashboardFetchOptions {
+ fetchOptions := &dashv2alpha1.DashboardFetchOptions{
+ Method: dashv2alpha1.DashboardHttpRequestMethod(schemaversion.GetStringValue(fetchMap, "method")),
+ Url: schemaversion.GetStringValue(fetchMap, "url"),
+ }
+
+ if body, ok := fetchMap["body"].(string); ok {
+ fetchOptions.Body = &body
+ }
+
+ // Convert queryParams (2D array of strings) - preserve empty arrays
+ if queryParams, ok := fetchMap["queryParams"].([]interface{}); ok {
+ fetchOptions.QueryParams = convert2DStringArrayPreserveEmpty(queryParams)
+ }
+
+ // Convert headers (2D array of strings) - preserve empty arrays
+ if headers, ok := fetchMap["headers"].([]interface{}); ok {
+ fetchOptions.Headers = convert2DStringArrayPreserveEmpty(headers)
+ }
+
+ return fetchOptions
+}
+
+func convertInfinityOptionsToV2(infinityMap map[string]interface{}) *dashv2alpha1.DashboardInfinityOptions {
+ infinityOptions := &dashv2alpha1.DashboardInfinityOptions{
+ Method: dashv2alpha1.DashboardHttpRequestMethod(schemaversion.GetStringValue(infinityMap, "method")),
+ Url: schemaversion.GetStringValue(infinityMap, "url"),
+ DatasourceUid: schemaversion.GetStringValue(infinityMap, "datasourceUid"),
+ }
+
+ if body, ok := infinityMap["body"].(string); ok {
+ infinityOptions.Body = &body
+ }
+
+ if queryParams, ok := infinityMap["queryParams"].([]interface{}); ok {
+ infinityOptions.QueryParams = convert2DStringArrayPreserveEmpty(queryParams)
+ }
+
+ if headers, ok := infinityMap["headers"].([]interface{}); ok {
+ infinityOptions.Headers = convert2DStringArrayPreserveEmpty(headers)
+ }
+
+ return infinityOptions
+}
+
+func convertActionVariablesToV2(variablesArray []interface{}) []dashv2alpha1.DashboardActionVariable {
+ if len(variablesArray) == 0 {
+ return nil
+ }
+
+ result := make([]dashv2alpha1.DashboardActionVariable, 0, len(variablesArray))
+ for _, variable := range variablesArray {
+ variableMap, ok := variable.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ result = append(result, dashv2alpha1.DashboardActionVariable{
+ Key: schemaversion.GetStringValue(variableMap, "key"),
+ Name: schemaversion.GetStringValue(variableMap, "name"),
+ Type: schemaversion.GetStringValue(variableMap, "type"),
+ })
+ }
+
+ return result
+}
+
+func convertActionStyleToV2(styleMap map[string]interface{}) *dashv2alpha1.DashboardV2alpha1ActionStyle {
+ style := &dashv2alpha1.DashboardV2alpha1ActionStyle{}
+
+ if backgroundColor, ok := styleMap["backgroundColor"].(string); ok {
+ style.BackgroundColor = &backgroundColor
+ }
+
+ return style
+}
+
+// convert2DStringArrayPreserveEmpty is like convert2DStringArray but returns
+// an empty slice (not nil) when input is empty, to ensure JSON marshals as []
+func convert2DStringArrayPreserveEmpty(arr []interface{}) [][]string {
+ // Return empty slice (not nil) to preserve [] in JSON output
+ result := make([][]string, 0, len(arr))
+ for _, item := range arr {
+ if innerArr, ok := item.([]interface{}); ok {
+ stringArr := make([]string, 0, len(innerArr))
+ for _, s := range innerArr {
+ if str, ok := s.(string); ok {
+ stringArr = append(stringArr, str)
+ }
+ }
+ result = append(result, stringArr)
+ }
+ }
+
+ return result
+}
+
// getAngularPanelMigration is a convenience wrapper around schemaversion.GetAngularPanelMigration.
// It checks if a panel type is an Angular panel and returns the new type to migrate to.
// Returns the new panel type if migration is needed, empty string otherwise.
diff --git a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go
index 46a2a533d41..f9f953f965d 100644
--- a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go
+++ b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go
@@ -71,11 +71,6 @@ func convertDashboardSpec_V2alpha1_to_V1beta1(in *dashv2alpha1.DashboardSpec) (m
if err != nil {
return nil, fmt.Errorf("failed to convert panels: %w", err)
}
- // Count total panels including those in collapsed rows
- totalPanelsConverted := countTotalPanels(panels)
- if totalPanelsConverted < len(in.Elements) {
- return nil, fmt.Errorf("some panels were not converted from v2alpha1 to v1beta1")
- }
if len(panels) > 0 {
dashboard["panels"] = panels
@@ -198,29 +193,6 @@ func convertLinksToV1(links []dashv2alpha1.DashboardDashboardLink) []map[string]
return result
}
-// countTotalPanels counts all panels including those nested in collapsed row panels.
-func countTotalPanels(panels []interface{}) int {
- count := 0
- for _, p := range panels {
- panel, ok := p.(map[string]interface{})
- if !ok {
- count++
- continue
- }
-
- // Check if this is a row panel with nested panels
- if panelType, ok := panel["type"].(string); ok && panelType == "row" {
- if nestedPanels, ok := panel["panels"].([]interface{}); ok {
- count += len(nestedPanels)
- }
- // Don't count the row itself as a panel element
- } else {
- count++
- }
- }
- return count
-}
-
// convertPanelsFromElementsAndLayout converts V2 layout structures to V1 panel arrays.
// V1 only supports a flat array of panels with row panels for grouping.
// This function dispatches to the appropriate converter based on layout type:
@@ -467,6 +439,11 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
rowPanel["title"] = *tab.Spec.Title
}
+ if tab.Spec.Repeat != nil && tab.Spec.Repeat.Value != "" {
+ // We only use value here as V1 doesn't support mode
+ rowPanel["repeat"] = tab.Spec.Repeat.Value
+ }
+
rowPanel["gridPos"] = map[string]interface{}{
"x": 0,
"y": currentY,
@@ -847,6 +824,21 @@ func convertAutoGridLayoutToPanelsWithOffset(elements map[string]dashv2alpha1.Da
},
}
+ // Convert AutoGridRepeatOptions to RepeatOptions if present
+ // AutoGridRepeatOptions only has mode and value; infer direction and maxPerRow from AutoGrid settings:
+ // - direction: always "h" (AutoGrid flows horizontally, left-to-right then wraps)
+ // - maxPerRow: from AutoGrid's maxColumnCount
+ if item.Spec.Repeat != nil {
+ directionH := dashv2alpha1.DashboardRepeatOptionsDirectionH
+ maxPerRow := int64(maxColumnCount)
+ gridItem.Spec.Repeat = &dashv2alpha1.DashboardRepeatOptions{
+ Mode: item.Spec.Repeat.Mode,
+ Value: item.Spec.Repeat.Value,
+ Direction: &directionH,
+ MaxPerRow: &maxPerRow,
+ }
+ }
+
panel, err := convertPanelFromElement(&element, &gridItem)
if err != nil {
return nil, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
@@ -1090,6 +1082,17 @@ func convertPanelKindToV1(panelKind *dashv2alpha1.DashboardPanelKind, panel map[
"id": t.Spec.Id,
"options": t.Spec.Options,
}
+ // Add disabled if set
+ if t.Spec.Disabled != nil {
+ transformation["disabled"] = *t.Spec.Disabled
+ }
+ // Add filter if set
+ if t.Spec.Filter != nil {
+ transformation["filter"] = map[string]interface{}{
+ "id": t.Spec.Filter.Id,
+ "options": t.Spec.Filter.Options,
+ }
+ }
transformations = append(transformations, transformation)
}
panel["transformations"] = transformations
@@ -1195,16 +1198,36 @@ func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, quer
// getPanelDatasource determines the panel-level datasource for V1.
// Returns:
// - Mixed datasource reference if queries use different datasources
+// - Mixed datasource reference if multiple queries use Dashboard datasource (they fetch from different panels)
+// - Dashboard datasource reference if a single query uses Dashboard datasource
// - First query's datasource if all queries use the same datasource
// - nil if no queries exist
// Compares based on V2 input without runtime resolution:
// - If query has explicit datasource.uid → use that UID and type
// - Else → use query.Kind as type (empty UID)
func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
+ const sharedDashboardQuery = "-- Dashboard --"
+
if len(queries) == 0 {
return nil
}
+ // Count how many queries use Dashboard datasource
+ // Multiple dashboard queries need mixed mode because they fetch from different panels
+ // which may have different underlying datasources
+ dashboardDsQueryCount := 0
+ for _, query := range queries {
+ if query.Spec.Datasource != nil && query.Spec.Datasource.Uid != nil && *query.Spec.Datasource.Uid == sharedDashboardQuery {
+ dashboardDsQueryCount++
+ }
+ }
+ if dashboardDsQueryCount > 1 {
+ return map[string]interface{}{
+ "type": "mixed",
+ "uid": "-- Mixed --",
+ }
+ }
+
var firstUID, firstType string
var hasFirst bool
@@ -1239,6 +1262,16 @@ func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[stri
}
}
+ // Handle case when a single query uses Dashboard datasource.
+ // This is needed for the frontend to properly activate and fetch data from source panels.
+ // See DashboardDatasourceBehaviour.tsx for more details.
+ if firstUID == sharedDashboardQuery {
+ return map[string]interface{}{
+ "type": "datasource",
+ "uid": sharedDashboardQuery,
+ }
+ }
+
// Not mixed - return the first query's datasource so the panel has a datasource set.
// This is required because the frontend's legacy PanelModel.PanelQueryRunner.run uses panel.datasource
// to resolve the datasource, and if undefined, it falls back to the default datasource
@@ -1955,9 +1988,18 @@ func convertFieldConfigDefaultsToV1(defaults *dashv2alpha1.DashboardFieldConfig)
if defaults.Writeable != nil {
result["writeable"] = *defaults.Writeable
}
+ if defaults.FieldMinMax != nil {
+ result["fieldMinMax"] = *defaults.FieldMinMax
+ }
+ if defaults.NullValueMode != nil {
+ result["nullValueMode"] = string(*defaults.NullValueMode)
+ }
if defaults.Links != nil {
result["links"] = defaults.Links
}
+ if len(defaults.Actions) > 0 {
+ result["actions"] = convertActionsToV1(defaults.Actions)
+ }
if defaults.Color != nil {
result["color"] = convertFieldColorToV1(defaults.Color)
}
@@ -2163,3 +2205,115 @@ func convertThresholdsToV1(thresholds *dashv2alpha1.DashboardThresholdsConfig) m
return thresholdsMap
}
+
+func convertActionsToV1(actions []dashv2alpha1.DashboardAction) []map[string]interface{} {
+ result := make([]map[string]interface{}, 0, len(actions))
+
+ for _, action := range actions {
+ actionMap := map[string]interface{}{
+ "type": string(action.Type),
+ "title": action.Title,
+ }
+
+ if action.Confirmation != nil {
+ actionMap["confirmation"] = *action.Confirmation
+ }
+
+ if action.OneClick != nil {
+ actionMap["oneClick"] = *action.OneClick
+ }
+
+ if action.Fetch != nil {
+ actionMap["fetch"] = convertFetchOptionsToV1(action.Fetch)
+ }
+
+ if action.Infinity != nil {
+ actionMap["infinity"] = convertInfinityOptionsToV1(action.Infinity)
+ }
+
+ if len(action.Variables) > 0 {
+ actionMap["variables"] = convertActionVariablesToV1(action.Variables)
+ }
+
+ if action.Style != nil {
+ styleMap := map[string]interface{}{}
+ if action.Style.BackgroundColor != nil {
+ styleMap["backgroundColor"] = *action.Style.BackgroundColor
+ }
+ if len(styleMap) > 0 {
+ actionMap["style"] = styleMap
+ }
+ }
+
+ result = append(result, actionMap)
+ }
+
+ return result
+}
+
+func convertFetchOptionsToV1(fetch *dashv2alpha1.DashboardFetchOptions) map[string]interface{} {
+ result := map[string]interface{}{
+ "method": string(fetch.Method),
+ "url": fetch.Url,
+ }
+
+ if fetch.Body != nil {
+ result["body"] = *fetch.Body
+ }
+
+ if len(fetch.QueryParams) > 0 {
+ result["queryParams"] = convert2DStringArrayToInterface(fetch.QueryParams)
+ }
+
+ if len(fetch.Headers) > 0 {
+ result["headers"] = convert2DStringArrayToInterface(fetch.Headers)
+ }
+
+ return result
+}
+
+func convertInfinityOptionsToV1(infinity *dashv2alpha1.DashboardInfinityOptions) map[string]interface{} {
+ result := map[string]interface{}{
+ "method": string(infinity.Method),
+ "url": infinity.Url,
+ "datasourceUid": infinity.DatasourceUid,
+ }
+
+ if infinity.Body != nil {
+ result["body"] = *infinity.Body
+ }
+
+ if len(infinity.QueryParams) > 0 {
+ result["queryParams"] = convert2DStringArrayToInterface(infinity.QueryParams)
+ }
+
+ if len(infinity.Headers) > 0 {
+ result["headers"] = convert2DStringArrayToInterface(infinity.Headers)
+ }
+
+ return result
+}
+
+func convertActionVariablesToV1(variables []dashv2alpha1.DashboardActionVariable) []map[string]interface{} {
+ result := make([]map[string]interface{}, 0, len(variables))
+ for _, v := range variables {
+ result = append(result, map[string]interface{}{
+ "key": v.Key,
+ "name": v.Name,
+ "type": v.Type,
+ })
+ }
+ return result
+}
+
+func convert2DStringArrayToInterface(arr [][]string) []interface{} {
+ result := make([]interface{}, 0, len(arr))
+ for _, innerArr := range arr {
+ interfaceArr := make([]interface{}, 0, len(innerArr))
+ for _, s := range innerArr {
+ interfaceArr = append(interfaceArr, s)
+ }
+ result = append(result, interfaceArr)
+ }
+ return result
+}
diff --git a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go
index 45803b6d7ec..8435c56d83f 100644
--- a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go
+++ b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go
@@ -310,6 +310,9 @@ func convertFieldConfig_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardFieldConfi
Links: in.Links,
NoValue: in.NoValue,
Custom: in.Custom,
+ FieldMinMax: in.FieldMinMax,
+ NullValueMode: (*dashv2beta1.DashboardNullValueMode)(in.NullValueMode),
+ Actions: convertActions_V2alpha1_to_V2beta1(in.Actions),
}
// Convert thresholds
@@ -1021,3 +1024,59 @@ func convertAnnotationMappings_V2alpha1_to_V2beta1(in map[string]dashv2alpha1.Da
}
return out
}
+
+func convertActions_V2alpha1_to_V2beta1(in []dashv2alpha1.DashboardAction) []dashv2beta1.DashboardAction {
+ if len(in) == 0 {
+ return nil
+ }
+
+ out := make([]dashv2beta1.DashboardAction, len(in))
+ for i, action := range in {
+ out[i] = dashv2beta1.DashboardAction{
+ Type: dashv2beta1.DashboardActionType(action.Type),
+ Title: action.Title,
+ Confirmation: action.Confirmation,
+ OneClick: action.OneClick,
+ }
+
+ if action.Fetch != nil {
+ out[i].Fetch = &dashv2beta1.DashboardFetchOptions{
+ Method: dashv2beta1.DashboardHttpRequestMethod(action.Fetch.Method),
+ Url: action.Fetch.Url,
+ Body: action.Fetch.Body,
+ QueryParams: action.Fetch.QueryParams,
+ Headers: action.Fetch.Headers,
+ }
+ }
+
+ if action.Infinity != nil {
+ out[i].Infinity = &dashv2beta1.DashboardInfinityOptions{
+ Method: dashv2beta1.DashboardHttpRequestMethod(action.Infinity.Method),
+ Url: action.Infinity.Url,
+ Body: action.Infinity.Body,
+ QueryParams: action.Infinity.QueryParams,
+ Headers: action.Infinity.Headers,
+ DatasourceUid: action.Infinity.DatasourceUid,
+ }
+ }
+
+ if len(action.Variables) > 0 {
+ out[i].Variables = make([]dashv2beta1.DashboardActionVariable, len(action.Variables))
+ for j, v := range action.Variables {
+ out[i].Variables[j] = dashv2beta1.DashboardActionVariable{
+ Key: v.Key,
+ Name: v.Name,
+ Type: v.Type,
+ }
+ }
+ }
+
+ if action.Style != nil {
+ out[i].Style = &dashv2beta1.DashboardV2beta1ActionStyle{
+ BackgroundColor: action.Style.BackgroundColor,
+ }
+ }
+ }
+
+ return out
+}
diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-loki/loki_query_splitting.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-loki/loki_query_splitting.v42.json
index a7beffa4cdc..8af239195cb 100644
--- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-loki/loki_query_splitting.v42.json
+++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-loki/loki_query_splitting.v42.json
@@ -219,8 +219,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -312,8 +311,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -492,8 +490,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -584,8 +581,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -676,8 +672,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -791,8 +786,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -906,8 +900,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -1022,8 +1015,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests.v42.json
index 635103053bf..87b63411976 100644
--- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests.v42.json
+++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests.v42.json
@@ -65,17 +65,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -136,17 +133,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -207,17 +201,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -271,7 +262,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -279,17 +269,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -342,7 +329,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -350,17 +336,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -414,7 +397,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -422,17 +404,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -485,7 +464,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -493,17 +471,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -682,7 +657,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -699,17 +673,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -764,7 +735,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -782,17 +752,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -847,7 +814,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -866,17 +832,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -931,7 +894,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"options": {
@@ -960,17 +922,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -1052,7 +1011,7 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "2",
+ "decimals": 2,
"mappings": [],
"max": 100,
"min": 0,
@@ -1060,17 +1019,14 @@
"mode": "absolute",
"steps": [
{
- "color": "#7EB26D",
- "index": 0
+ "color": "#7EB26D"
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
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 61f092d491e..7fdd474df87 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
@@ -2117,7 +2117,7 @@
}
],
"title": "Numeric, no series",
- "type": "gauge"
+ "type": "radialbar"
},
{
"datasource": {
@@ -2183,7 +2183,7 @@
}
],
"title": "Non-numeric",
- "type": "gauge"
+ "type": "radialbar"
}
],
"preload": false,
@@ -2201,4 +2201,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-histogram/histogram_tests.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-histogram/histogram_tests.v42.json
index 7e392bd55d0..6c521eaec9b 100644
--- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-histogram/histogram_tests.v42.json
+++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-histogram/histogram_tests.v42.json
@@ -58,8 +58,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -127,8 +126,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -196,8 +194,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -277,8 +274,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -355,8 +351,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -448,8 +443,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -536,8 +530,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -619,8 +612,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -702,8 +694,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -785,8 +776,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -850,8 +840,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-piechart/panel_test_piechart.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-piechart/panel_test_piechart.v42.json
index 5b07f246ae3..f705124be5b 100644
--- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-piechart/panel_test_piechart.v42.json
+++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-piechart/panel_test_piechart.v42.json
@@ -290,7 +290,7 @@
],
"legend": {
"displayMode": "table",
- "placement": "right",
+ "placement": "bottom",
"showLegend": true,
"values": [
"percent"
@@ -304,7 +304,7 @@
"fields": "",
"values": false
},
- "showLegend": true,
+ "showLegend": false,
"strokeWidth": 1,
"text": {}
},
@@ -323,15 +323,6 @@
}
],
"title": "Percent",
- "transformations": [
- {
- "id": "renameByRegex",
- "options": {
- "regex": "^Backend-(.*)$",
- "renamePattern": "b-$1"
- }
- }
- ],
"type": "piechart"
},
{
@@ -375,7 +366,7 @@
],
"legend": {
"displayMode": "table",
- "placement": "right",
+ "placement": "bottom",
"showLegend": true,
"values": [
"value"
@@ -389,7 +380,7 @@
"fields": "",
"values": false
},
- "showLegend": true,
+ "showLegend": false,
"strokeWidth": 1,
"text": {}
},
@@ -408,15 +399,6 @@
}
],
"title": "Value",
- "transformations": [
- {
- "id": "renameByRegex",
- "options": {
- "regex": "(.*)",
- "renamePattern": "$1-how-much-wood-could-a-woodchuck-chuck-if-a-woodchuck-could-chuck-wood"
- }
- }
- ],
"type": "piechart"
},
{
diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-tooltip-color-test.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-tooltip-color-test.v42.json
index 417ea1661e1..f28fee864e5 100644
--- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-tooltip-color-test.v42.json
+++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-tooltip-color-test.v42.json
@@ -61,8 +61,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -148,8 +147,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -235,8 +233,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -322,8 +319,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -416,8 +412,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -510,8 +505,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -604,8 +598,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/apps/plugins/Makefile b/apps/plugins/Makefile
index 230bfd4149a..2db266ef19b 100644
--- a/apps/plugins/Makefile
+++ b/apps/plugins/Makefile
@@ -1,9 +1,16 @@
include ../sdk.mk
-.PHONY: generate # Run Grafana App SDK code generation
-generate: install-app-sdk update-app-sdk
+.PHONY: internal-generate # Run Grafana App SDK code generation
+internal-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
+ --defencoding=none
+
+.PHONY: generate
+generate: internal-generate # copy files to packages/grafana-runtime/src/services/pluginMeta/types
+ rm -f ./packages/grafana-runtime/src/services/pluginMeta/types/*.ts
+ cp plugin/src/generated/meta/v0alpha1/meta_object_gen.ts ../../packages/grafana-runtime/src/services/pluginMeta/types/meta_object_gen.ts
+ cp plugin/src/generated/meta/v0alpha1/types.spec.gen.ts ../../packages/grafana-runtime/src/services/pluginMeta/types/types.spec.gen.ts
+ cp plugin/src/generated/meta/v0alpha1/types.status.gen.ts ../../packages/grafana-runtime/src/services/pluginMeta/types/types.status.gen.ts
\ No newline at end of file
diff --git a/apps/plugins/README.md b/apps/plugins/README.md
index 7f91dd6ea12..f21fa6701b5 100644
--- a/apps/plugins/README.md
+++ b/apps/plugins/README.md
@@ -4,8 +4,7 @@ API documentation is available at http://localhost:3000/swagger?api=plugins.graf
## Codegen
-- Go: `make generate`
-- Frontend: Follow instructions in this [README](../..//packages/grafana-api-clients/README.md)
+- Go and TypeScript: `make generate`
## Plugin sync
diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod
index e4866731967..7e9e5f47876 100644
--- a/apps/plugins/go.mod
+++ b/apps/plugins/go.mod
@@ -97,7 +97,7 @@ require (
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
- github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // indirect
+ github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f // indirect
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum
index 29738a48cca..c991111f90e 100644
--- a/apps/plugins/go.sum
+++ b/apps/plugins/go.sum
@@ -215,8 +215,8 @@ 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/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-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
-github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
+github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f h1:3bXOyht68qkfvD6Y8z8XoenFbytSSOIkr/s+AqRzj0o=
+github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f/go.mod h1:Ji0SfJChcwjgq8ljy6Y5CcYfHfAYKXjKYeysOoDS/6s=
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg=
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY=
diff --git a/apps/plugins/kinds/manifest.cue b/apps/plugins/kinds/manifest.cue
index f624dc117bc..680a0f7565d 100644
--- a/apps/plugins/kinds/manifest.cue
+++ b/apps/plugins/kinds/manifest.cue
@@ -11,7 +11,7 @@ manifest: {
v0alpha1Version: {
served: true
codegen: {
- ts: {enabled: false}
+ ts: {enabled: true}
go: {enabled: true}
}
kinds: [
diff --git a/apps/plugins/kinds/meta.cue b/apps/plugins/kinds/meta.cue
index 01dc45adf77..479a9111d24 100644
--- a/apps/plugins/kinds/meta.cue
+++ b/apps/plugins/kinds/meta.cue
@@ -18,9 +18,6 @@ metaV0Alpha1: {
type?: "grafana" | "commercial" | "community" | "private" | "private-glob"
org?: string
}
- angular?: {
- detected: bool
- }
translations?: [string]: string
// +listType=atomic
children?: [...string]
diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/meta_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/meta_spec_gen.go
index 631febbe2fa..141e9e5ad82 100644
--- a/apps/plugins/pkg/apis/plugins/v0alpha1/meta_spec_gen.go
+++ b/apps/plugins/pkg/apis/plugins/v0alpha1/meta_spec_gen.go
@@ -215,7 +215,6 @@ type MetaSpec struct {
Module *MetaV0alpha1SpecModule `json:"module,omitempty"`
BaseURL *string `json:"baseURL,omitempty"`
Signature *MetaV0alpha1SpecSignature `json:"signature,omitempty"`
- Angular *MetaV0alpha1SpecAngular `json:"angular,omitempty"`
Translations map[string]string `json:"translations,omitempty"`
// +listType=atomic
Children []string `json:"children,omitempty"`
@@ -461,16 +460,6 @@ func NewMetaV0alpha1SpecSignature() *MetaV0alpha1SpecSignature {
return &MetaV0alpha1SpecSignature{}
}
-// +k8s:openapi-gen=true
-type MetaV0alpha1SpecAngular struct {
- Detected bool `json:"detected"`
-}
-
-// NewMetaV0alpha1SpecAngular creates a new MetaV0alpha1SpecAngular object.
-func NewMetaV0alpha1SpecAngular() *MetaV0alpha1SpecAngular {
- return &MetaV0alpha1SpecAngular{}
-}
-
// +k8s:openapi-gen=true
type MetaJSONDataType string
diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go
index 0c52665e75d..f37c14ed0cf 100644
--- a/apps/plugins/pkg/apis/plugins_manifest.go
+++ b/apps/plugins/pkg/apis/plugins_manifest.go
@@ -23,7 +23,7 @@ var (
rawSchemaPluginv0alpha1 = []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"},"Plugin":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version"],"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"}}`)
versionSchemaPluginv0alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaPluginv0alpha1, &versionSchemaPluginv0alpha1)
- rawSchemaMetav0alpha1 = []byte(`{"Dependencies":{"additionalProperties":false,"properties":{"extensions":{"additionalProperties":false,"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"EnterpriseFeatures":{"additionalProperties":false,"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"Extensions":{"additionalProperties":false,"properties":{"addedComponents":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedFunctions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"IAM":{"additionalProperties":false,"properties":{"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"Include":{"additionalProperties":false,"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer","None"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"Info":{"additionalProperties":false,"properties":{"author":{"additionalProperties":false,"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"additionalProperties":false,"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"JSONData":{"additionalProperties":false,"description":"JSON configuration schema for Grafana plugins\nConverted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json","properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"$ref":"#/components/schemas/Dependencies","description":"Dependency information"},"enterpriseFeatures":{"$ref":"#/components/schemas/EnterpriseFeatures"},"executable":{"type":"string"},"extensions":{"$ref":"#/components/schemas/Extensions"},"hideFromList":{"type":"boolean"},"iam":{"$ref":"#/components/schemas/IAM"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Include"},"type":"array"},"info":{"$ref":"#/components/schemas/Info","description":"Metadata for the plugin"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"$ref":"#/components/schemas/QueryOptions"},"roles":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Role"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Route"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"suggestions":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"},"Meta":{"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"},"QueryOptions":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"Role":{"additionalProperties":false,"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"additionalProperties":false,"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"Route":{"additionalProperties":false,"properties":{"body":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"angular":{"additionalProperties":false,"properties":{"detected":{"type":"boolean"}},"required":["detected"],"type":"object"},"baseURL":{"type":"string"},"children":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"class":{"enum":["core","external"],"type":"string"},"module":{"additionalProperties":false,"properties":{"hash":{"type":"string"},"loadingStrategy":{"enum":["fetch","script"],"type":"string"},"path":{"type":"string"}},"required":["path"],"type":"object"},"pluginJson":{"$ref":"#/components/schemas/JSONData"},"signature":{"additionalProperties":false,"properties":{"org":{"type":"string"},"status":{"enum":["internal","valid","invalid","modified","unsigned"],"type":"string"},"type":{"enum":["grafana","commercial","community","private","private-glob"],"type":"string"}},"required":["status"],"type":"object"},"translations":{"additionalProperties":{"type":"string"},"type":"object"}},"required":["pluginJson","class"],"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"}}`)
+ rawSchemaMetav0alpha1 = []byte(`{"Dependencies":{"additionalProperties":false,"properties":{"extensions":{"additionalProperties":false,"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"EnterpriseFeatures":{"additionalProperties":false,"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"Extensions":{"additionalProperties":false,"properties":{"addedComponents":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedFunctions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"IAM":{"additionalProperties":false,"properties":{"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"Include":{"additionalProperties":false,"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer","None"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"Info":{"additionalProperties":false,"properties":{"author":{"additionalProperties":false,"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"additionalProperties":false,"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"JSONData":{"additionalProperties":false,"description":"JSON configuration schema for Grafana plugins\nConverted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json","properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"$ref":"#/components/schemas/Dependencies","description":"Dependency information"},"enterpriseFeatures":{"$ref":"#/components/schemas/EnterpriseFeatures"},"executable":{"type":"string"},"extensions":{"$ref":"#/components/schemas/Extensions"},"hideFromList":{"type":"boolean"},"iam":{"$ref":"#/components/schemas/IAM"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Include"},"type":"array"},"info":{"$ref":"#/components/schemas/Info","description":"Metadata for the plugin"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"$ref":"#/components/schemas/QueryOptions"},"roles":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Role"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Route"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"suggestions":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"},"Meta":{"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"},"QueryOptions":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"Role":{"additionalProperties":false,"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"additionalProperties":false,"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"Route":{"additionalProperties":false,"properties":{"body":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"baseURL":{"type":"string"},"children":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"class":{"enum":["core","external"],"type":"string"},"module":{"additionalProperties":false,"properties":{"hash":{"type":"string"},"loadingStrategy":{"enum":["fetch","script"],"type":"string"},"path":{"type":"string"}},"required":["path"],"type":"object"},"pluginJson":{"$ref":"#/components/schemas/JSONData"},"signature":{"additionalProperties":false,"properties":{"org":{"type":"string"},"status":{"enum":["internal","valid","invalid","modified","unsigned"],"type":"string"},"type":{"enum":["grafana","commercial","community","private","private-glob"],"type":"string"}},"required":["status"],"type":"object"},"translations":{"additionalProperties":{"type":"string"},"type":"object"}},"required":["pluginJson","class"],"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"}}`)
versionSchemaMetav0alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaMetav0alpha1, &versionSchemaMetav0alpha1)
)
diff --git a/apps/plugins/pkg/app/meta/converter.go b/apps/plugins/pkg/app/meta/converter.go
index b8c0c4371d7..70a1bc78b0c 100644
--- a/apps/plugins/pkg/app/meta/converter.go
+++ b/apps/plugins/pkg/app/meta/converter.go
@@ -565,10 +565,6 @@ func pluginStorePluginToMeta(plugin pluginstore.Plugin, loadingStrategy plugins.
metaSpec.Children = plugin.Children
}
- metaSpec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
- Detected: plugin.Angular.Detected,
- }
-
if len(plugin.Translations) > 0 {
metaSpec.Translations = plugin.Translations
}
@@ -668,10 +664,6 @@ func pluginToMetaSpec(plugin *plugins.Plugin) pluginsv0alpha1.MetaSpec {
metaSpec.Children = children
}
- metaSpec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
- Detected: plugin.Angular.Detected,
- }
-
if len(plugin.Translations) > 0 {
metaSpec.Translations = plugin.Translations
}
@@ -712,8 +704,7 @@ type grafanaComPluginVersionMeta struct {
Rel string `json:"rel"`
Href string `json:"href"`
} `json:"links"`
- AngularDetected bool `json:"angularDetected"`
- Scopes []string `json:"scopes"`
+ Scopes []string `json:"scopes"`
}
// grafanaComPluginVersionMetaToMetaSpec converts a grafanaComPluginVersionMeta to a pluginsv0alpha1.MetaSpec.
@@ -753,10 +744,5 @@ func grafanaComPluginVersionMetaToMetaSpec(gcomMeta grafanaComPluginVersionMeta)
metaSpec.Signature = signature
}
- // Set angular info
- metaSpec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
- Detected: gcomMeta.AngularDetected,
- }
-
return metaSpec
}
diff --git a/apps/plugins/plugin/src/generated/meta/v0alpha1/meta_object_gen.ts b/apps/plugins/plugin/src/generated/meta/v0alpha1/meta_object_gen.ts
new file mode 100644
index 00000000000..044ec1f4cd8
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/meta/v0alpha1/meta_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 Meta {
+ kind: string;
+ apiVersion: string;
+ metadata: Metadata;
+ spec: Spec;
+ status: Status;
+}
diff --git a/apps/plugins/plugin/src/generated/meta/v0alpha1/types.metadata.gen.ts b/apps/plugins/plugin/src/generated/meta/v0alpha1/types.metadata.gen.ts
new file mode 100644
index 00000000000..4377f3c1d08
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/meta/v0alpha1/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/plugins/plugin/src/generated/meta/v0alpha1/types.spec.gen.ts b/apps/plugins/plugin/src/generated/meta/v0alpha1/types.spec.gen.ts
new file mode 100644
index 00000000000..51845e98454
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/meta/v0alpha1/types.spec.gen.ts
@@ -0,0 +1,278 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+// JSON configuration schema for Grafana plugins
+// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json
+export interface JSONData {
+ // Unique name of the plugin
+ id: string;
+ // Plugin type
+ type: "app" | "datasource" | "panel" | "renderer";
+ // Human-readable name of the plugin
+ name: string;
+ // Metadata for the plugin
+ info: Info;
+ // Dependency information
+ dependencies: Dependencies;
+ // Optional fields
+ alerting?: boolean;
+ annotations?: boolean;
+ autoEnabled?: boolean;
+ backend?: boolean;
+ buildMode?: string;
+ builtIn?: boolean;
+ category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other";
+ enterpriseFeatures?: EnterpriseFeatures;
+ executable?: string;
+ hideFromList?: boolean;
+ // +listType=atomic
+ includes?: Include[];
+ logs?: boolean;
+ metrics?: boolean;
+ multiValueFilterOperators?: boolean;
+ pascalName?: string;
+ preload?: boolean;
+ queryOptions?: QueryOptions;
+ // +listType=atomic
+ routes?: Route[];
+ skipDataQuery?: boolean;
+ state?: "alpha" | "beta";
+ streaming?: boolean;
+ suggestions?: boolean;
+ tracing?: boolean;
+ iam?: IAM;
+ // +listType=atomic
+ roles?: Role[];
+ extensions?: Extensions;
+}
+
+export const defaultJSONData = (): JSONData => ({
+ id: "",
+ type: "app",
+ name: "",
+ info: defaultInfo(),
+ dependencies: defaultDependencies(),
+});
+
+export interface Info {
+ // Required fields
+ // +listType=set
+ keywords: string[];
+ logos: {
+ small: string;
+ large: string;
+ };
+ updated: string;
+ version: string;
+ // Optional fields
+ author?: {
+ name?: string;
+ email?: string;
+ url?: string;
+ };
+ description?: string;
+ // +listType=atomic
+ links?: {
+ name?: string;
+ url?: string;
+ }[];
+ // +listType=atomic
+ screenshots?: {
+ name?: string;
+ path?: string;
+ }[];
+}
+
+export const defaultInfo = (): Info => ({
+ keywords: [],
+ logos: {
+ small: "",
+ large: "",
+},
+ updated: "",
+ version: "",
+});
+
+export interface Dependencies {
+ // Required field
+ grafanaDependency: string;
+ // Optional fields
+ grafanaVersion?: string;
+ // +listType=set
+ // +listMapKey=id
+ plugins?: {
+ id: string;
+ type: "app" | "datasource" | "panel";
+ name: string;
+ }[];
+ extensions?: {
+ // +listType=set
+ exposedComponents?: string[];
+ };
+}
+
+export const defaultDependencies = (): Dependencies => ({
+ grafanaDependency: "",
+});
+
+export interface EnterpriseFeatures {
+ // Allow additional properties
+ healthDiagnosticsErrors?: boolean;
+}
+
+export const defaultEnterpriseFeatures = (): EnterpriseFeatures => ({
+ healthDiagnosticsErrors: false,
+});
+
+export interface Include {
+ uid?: string;
+ type?: "dashboard" | "page" | "panel" | "datasource";
+ name?: string;
+ component?: string;
+ role?: "Admin" | "Editor" | "Viewer" | "None";
+ action?: string;
+ path?: string;
+ addToNav?: boolean;
+ defaultNav?: boolean;
+ icon?: string;
+}
+
+export const defaultInclude = (): Include => ({
+});
+
+export interface QueryOptions {
+ maxDataPoints?: boolean;
+ minInterval?: boolean;
+ cacheTimeout?: boolean;
+}
+
+export const defaultQueryOptions = (): QueryOptions => ({
+});
+
+export interface Route {
+ path?: string;
+ method?: string;
+ url?: string;
+ reqSignedIn?: boolean;
+ reqRole?: string;
+ reqAction?: string;
+ // +listType=atomic
+ headers?: string[];
+ body?: Record;
+ tokenAuth?: {
+ url?: string;
+ // +listType=set
+ scopes?: string[];
+ params?: Record;
+ };
+ jwtTokenAuth?: {
+ url?: string;
+ // +listType=set
+ scopes?: string[];
+ params?: Record;
+ };
+ // +listType=atomic
+ urlParams?: {
+ name?: string;
+ content?: string;
+ }[];
+}
+
+export const defaultRoute = (): Route => ({
+});
+
+export interface IAM {
+ // +listType=atomic
+ permissions?: {
+ action?: string;
+ scope?: string;
+ }[];
+}
+
+export const defaultIAM = (): IAM => ({
+});
+
+export interface Role {
+ role?: {
+ name?: string;
+ description?: string;
+ // +listType=atomic
+ permissions?: {
+ action?: string;
+ scope?: string;
+ }[];
+ };
+ // +listType=set
+ grants?: string[];
+}
+
+export const defaultRole = (): Role => ({
+});
+
+export interface Extensions {
+ // +listType=atomic
+ addedComponents?: {
+ // +listType=set
+ targets: string[];
+ title: string;
+ description?: string;
+ }[];
+ // +listType=atomic
+ addedLinks?: {
+ // +listType=set
+ targets: string[];
+ title: string;
+ description?: string;
+ }[];
+ // +listType=atomic
+ addedFunctions?: {
+ // +listType=set
+ targets: string[];
+ title: string;
+ description?: string;
+ }[];
+ // +listType=set
+ // +listMapKey=id
+ exposedComponents?: {
+ id: string;
+ title?: string;
+ description?: string;
+ }[];
+ // +listType=set
+ // +listMapKey=id
+ extensionPoints?: {
+ id: string;
+ title?: string;
+ description?: string;
+ }[];
+}
+
+export const defaultExtensions = (): Extensions => ({
+});
+
+export interface Spec {
+ pluginJson: JSONData;
+ class: "core" | "external";
+ module?: {
+ path: string;
+ hash?: string;
+ loadingStrategy?: "fetch" | "script";
+ };
+ baseURL?: string;
+ signature?: {
+ status: "internal" | "valid" | "invalid" | "modified" | "unsigned";
+ type?: "grafana" | "commercial" | "community" | "private" | "private-glob";
+ org?: string;
+ };
+ angular?: {
+ detected: boolean;
+ };
+ translations?: Record;
+ // +listType=atomic
+ children?: string[];
+}
+
+export const defaultSpec = (): Spec => ({
+ pluginJson: defaultJSONData(),
+ class: "core",
+});
+
diff --git a/apps/plugins/plugin/src/generated/meta/v0alpha1/types.status.gen.ts b/apps/plugins/plugin/src/generated/meta/v0alpha1/types.status.gen.ts
new file mode 100644
index 00000000000..01be8df7961
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/meta/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/plugins/plugin/src/generated/plugin/v0alpha1/plugin_object_gen.ts b/apps/plugins/plugin/src/generated/plugin/v0alpha1/plugin_object_gen.ts
new file mode 100644
index 00000000000..c4e625fc418
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/plugin/v0alpha1/plugin_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 Plugin {
+ kind: string;
+ apiVersion: string;
+ metadata: Metadata;
+ spec: Spec;
+ status: Status;
+}
diff --git a/apps/plugins/plugin/src/generated/plugin/v0alpha1/types.metadata.gen.ts b/apps/plugins/plugin/src/generated/plugin/v0alpha1/types.metadata.gen.ts
new file mode 100644
index 00000000000..4377f3c1d08
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/plugin/v0alpha1/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/plugins/plugin/src/generated/plugin/v0alpha1/types.spec.gen.ts b/apps/plugins/plugin/src/generated/plugin/v0alpha1/types.spec.gen.ts
new file mode 100644
index 00000000000..6b7824b8941
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/plugin/v0alpha1/types.spec.gen.ts
@@ -0,0 +1,13 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+export interface Spec {
+ id: string;
+ version: string;
+ url?: string;
+}
+
+export const defaultSpec = (): Spec => ({
+ id: "",
+ version: "",
+});
+
diff --git a/apps/plugins/plugin/src/generated/plugin/v0alpha1/types.status.gen.ts b/apps/plugins/plugin/src/generated/plugin/v0alpha1/types.status.gen.ts
new file mode 100644
index 00000000000..01be8df7961
--- /dev/null
+++ b/apps/plugins/plugin/src/generated/plugin/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/provisioning/pkg/apis/provisioning/v0alpha1/connections.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go
index f9f8dcc8382..2738af49db1 100644
--- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go
@@ -32,7 +32,7 @@ type ConnectionSecure struct {
// Token is the reference of the token used to act as the Connection.
// This value is stored securely and cannot be read back
- Token common.InlineSecureValue `json:"webhook,omitzero,omitempty"`
+ Token common.InlineSecureValue `json:"token,omitzero,omitempty"`
}
func (v ConnectionSecure) IsZero() bool {
diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
index 11788142e94..4db11489c98 100644
--- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
@@ -320,7 +320,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref common.Reference
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
},
},
- "webhook": {
+ "token": {
SchemaProps: spec.SchemaProps{
Description: "Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back",
Default: map[string]interface{}{},
diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
index 3a54dcf2a5e..72567e04b90 100644
--- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
@@ -22,7 +22,6 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
-API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSecure,Token
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSpec,GitHub
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs
diff --git a/apps/provisioning/pkg/connection/connection.go b/apps/provisioning/pkg/connection/connection.go
new file mode 100644
index 00000000000..d2043b5af4a
--- /dev/null
+++ b/apps/provisioning/pkg/connection/connection.go
@@ -0,0 +1,16 @@
+package connection
+
+import (
+ "context"
+)
+
+//go:generate mockery --name Connection --structname MockConnection --inpackage --filename connection_mock.go --with-expecter
+type Connection interface {
+ // Validate ensures the resource _looks_ correct.
+ // It should be called before trying to upsert a resource into the Kubernetes API server.
+ // This is not an indication that the connection information works, just that they are reasonably configured.
+ Validate(ctx context.Context) error
+
+ // Mutate performs in place mutation of the underneath resource.
+ Mutate(context.Context) error
+}
diff --git a/apps/provisioning/pkg/connection/connection_mock.go b/apps/provisioning/pkg/connection/connection_mock.go
new file mode 100644
index 00000000000..3867059d432
--- /dev/null
+++ b/apps/provisioning/pkg/connection/connection_mock.go
@@ -0,0 +1,128 @@
+// Code generated by mockery v2.53.4. DO NOT EDIT.
+
+package connection
+
+import (
+ context "context"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// MockConnection is an autogenerated mock type for the Connection type
+type MockConnection struct {
+ mock.Mock
+}
+
+type MockConnection_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockConnection) EXPECT() *MockConnection_Expecter {
+ return &MockConnection_Expecter{mock: &_m.Mock}
+}
+
+// Mutate provides a mock function with given fields: _a0
+func (_m *MockConnection) Mutate(_a0 context.Context) error {
+ ret := _m.Called(_a0)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Mutate")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(context.Context) error); ok {
+ r0 = rf(_a0)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockConnection_Mutate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mutate'
+type MockConnection_Mutate_Call struct {
+ *mock.Call
+}
+
+// Mutate is a helper method to define mock.On call
+// - _a0 context.Context
+func (_e *MockConnection_Expecter) Mutate(_a0 interface{}) *MockConnection_Mutate_Call {
+ return &MockConnection_Mutate_Call{Call: _e.mock.On("Mutate", _a0)}
+}
+
+func (_c *MockConnection_Mutate_Call) Run(run func(_a0 context.Context)) *MockConnection_Mutate_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context))
+ })
+ return _c
+}
+
+func (_c *MockConnection_Mutate_Call) Return(_a0 error) *MockConnection_Mutate_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockConnection_Mutate_Call) RunAndReturn(run func(context.Context) error) *MockConnection_Mutate_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// Validate provides a mock function with given fields: ctx
+func (_m *MockConnection) Validate(ctx context.Context) error {
+ ret := _m.Called(ctx)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Validate")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(context.Context) error); ok {
+ r0 = rf(ctx)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockConnection_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
+type MockConnection_Validate_Call struct {
+ *mock.Call
+}
+
+// Validate is a helper method to define mock.On call
+// - ctx context.Context
+func (_e *MockConnection_Expecter) Validate(ctx interface{}) *MockConnection_Validate_Call {
+ return &MockConnection_Validate_Call{Call: _e.mock.On("Validate", ctx)}
+}
+
+func (_c *MockConnection_Validate_Call) Run(run func(ctx context.Context)) *MockConnection_Validate_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context))
+ })
+ return _c
+}
+
+func (_c *MockConnection_Validate_Call) Return(_a0 error) *MockConnection_Validate_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockConnection_Validate_Call) RunAndReturn(run func(context.Context) error) *MockConnection_Validate_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockConnection creates a new instance of MockConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockConnection(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockConnection {
+ mock := &MockConnection{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/apps/provisioning/pkg/connection/extra_mock.go b/apps/provisioning/pkg/connection/extra_mock.go
new file mode 100644
index 00000000000..cc2a1f3d5e2
--- /dev/null
+++ b/apps/provisioning/pkg/connection/extra_mock.go
@@ -0,0 +1,141 @@
+// Code generated by mockery v2.53.4. DO NOT EDIT.
+
+package connection
+
+import (
+ context "context"
+
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// MockExtra is an autogenerated mock type for the Extra type
+type MockExtra struct {
+ mock.Mock
+}
+
+type MockExtra_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockExtra) EXPECT() *MockExtra_Expecter {
+ return &MockExtra_Expecter{mock: &_m.Mock}
+}
+
+// Build provides a mock function with given fields: ctx, r
+func (_m *MockExtra) Build(ctx context.Context, r *v0alpha1.Connection) (Connection, error) {
+ ret := _m.Called(ctx, r)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Build")
+ }
+
+ var r0 Connection
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) (Connection, error)); ok {
+ return rf(ctx, r)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) Connection); ok {
+ r0 = rf(ctx, r)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(Connection)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Connection) error); ok {
+ r1 = rf(ctx, r)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// MockExtra_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build'
+type MockExtra_Build_Call struct {
+ *mock.Call
+}
+
+// Build is a helper method to define mock.On call
+// - ctx context.Context
+// - r *v0alpha1.Connection
+func (_e *MockExtra_Expecter) Build(ctx interface{}, r interface{}) *MockExtra_Build_Call {
+ return &MockExtra_Build_Call{Call: _e.mock.On("Build", ctx, r)}
+}
+
+func (_c *MockExtra_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Connection)) *MockExtra_Build_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(*v0alpha1.Connection))
+ })
+ return _c
+}
+
+func (_c *MockExtra_Build_Call) Return(_a0 Connection, _a1 error) *MockExtra_Build_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *MockExtra_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Connection) (Connection, error)) *MockExtra_Build_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// Type provides a mock function with no fields
+func (_m *MockExtra) Type() v0alpha1.ConnectionType {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for Type")
+ }
+
+ var r0 v0alpha1.ConnectionType
+ if rf, ok := ret.Get(0).(func() v0alpha1.ConnectionType); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Get(0).(v0alpha1.ConnectionType)
+ }
+
+ return r0
+}
+
+// MockExtra_Type_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Type'
+type MockExtra_Type_Call struct {
+ *mock.Call
+}
+
+// Type is a helper method to define mock.On call
+func (_e *MockExtra_Expecter) Type() *MockExtra_Type_Call {
+ return &MockExtra_Type_Call{Call: _e.mock.On("Type")}
+}
+
+func (_c *MockExtra_Type_Call) Run(run func()) *MockExtra_Type_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run()
+ })
+ return _c
+}
+
+func (_c *MockExtra_Type_Call) Return(_a0 v0alpha1.ConnectionType) *MockExtra_Type_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockExtra_Type_Call) RunAndReturn(run func() v0alpha1.ConnectionType) *MockExtra_Type_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockExtra creates a new instance of MockExtra. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockExtra(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockExtra {
+ mock := &MockExtra{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/apps/provisioning/pkg/connection/factory.go b/apps/provisioning/pkg/connection/factory.go
new file mode 100644
index 00000000000..4a0e46d84d2
--- /dev/null
+++ b/apps/provisioning/pkg/connection/factory.go
@@ -0,0 +1,75 @@
+package connection
+
+import (
+ "context"
+ "fmt"
+ "sort"
+
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+)
+
+//go:generate mockery --name=Extra --structname=MockExtra --inpackage --filename=extra_mock.go --with-expecter
+type Extra interface {
+ Type() provisioning.ConnectionType
+ Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
+}
+
+//go:generate mockery --name=Factory --structname=MockFactory --inpackage --filename=factory_mock.go --with-expecter
+type Factory interface {
+ Types() []provisioning.ConnectionType
+ Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
+}
+
+type factory struct {
+ extras map[provisioning.ConnectionType]Extra
+ enabled map[provisioning.ConnectionType]struct{}
+}
+
+func ProvideFactory(enabled map[provisioning.ConnectionType]struct{}, extras []Extra) (Factory, error) {
+ f := &factory{
+ enabled: enabled,
+ extras: make(map[provisioning.ConnectionType]Extra, len(extras)),
+ }
+
+ for _, e := range extras {
+ if _, exists := f.extras[e.Type()]; exists {
+ return nil, fmt.Errorf("connection type %q is already registered", e.Type())
+ }
+ f.extras[e.Type()] = e
+ }
+
+ return f, nil
+}
+
+func (f *factory) Types() []provisioning.ConnectionType {
+ var types []provisioning.ConnectionType
+ for t := range f.enabled {
+ if _, exists := f.extras[t]; exists {
+ types = append(types, t)
+ }
+ }
+
+ sort.Slice(types, func(i, j int) bool {
+ return string(types[i]) < string(types[j])
+ })
+
+ return types
+}
+
+func (f *factory) Build(ctx context.Context, c *provisioning.Connection) (Connection, error) {
+ for _, e := range f.extras {
+ if e.Type() == c.Spec.Type {
+ if _, enabled := f.enabled[e.Type()]; !enabled {
+ return nil, fmt.Errorf("connection type %q is not enabled", e.Type())
+ }
+
+ return e.Build(ctx, c)
+ }
+ }
+
+ return nil, fmt.Errorf("connection type %q is not supported", c.Spec.Type)
+}
+
+var (
+ _ Factory = (*factory)(nil)
+)
diff --git a/apps/provisioning/pkg/connection/factory_mock.go b/apps/provisioning/pkg/connection/factory_mock.go
new file mode 100644
index 00000000000..8fd7023920f
--- /dev/null
+++ b/apps/provisioning/pkg/connection/factory_mock.go
@@ -0,0 +1,143 @@
+// Code generated by mockery v2.53.4. DO NOT EDIT.
+
+package connection
+
+import (
+ context "context"
+
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// MockFactory is an autogenerated mock type for the Factory type
+type MockFactory struct {
+ mock.Mock
+}
+
+type MockFactory_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockFactory) EXPECT() *MockFactory_Expecter {
+ return &MockFactory_Expecter{mock: &_m.Mock}
+}
+
+// Build provides a mock function with given fields: ctx, r
+func (_m *MockFactory) Build(ctx context.Context, r *v0alpha1.Connection) (Connection, error) {
+ ret := _m.Called(ctx, r)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Build")
+ }
+
+ var r0 Connection
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) (Connection, error)); ok {
+ return rf(ctx, r)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) Connection); ok {
+ r0 = rf(ctx, r)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(Connection)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Connection) error); ok {
+ r1 = rf(ctx, r)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// MockFactory_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build'
+type MockFactory_Build_Call struct {
+ *mock.Call
+}
+
+// Build is a helper method to define mock.On call
+// - ctx context.Context
+// - r *v0alpha1.Connection
+func (_e *MockFactory_Expecter) Build(ctx interface{}, r interface{}) *MockFactory_Build_Call {
+ return &MockFactory_Build_Call{Call: _e.mock.On("Build", ctx, r)}
+}
+
+func (_c *MockFactory_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Connection)) *MockFactory_Build_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(*v0alpha1.Connection))
+ })
+ return _c
+}
+
+func (_c *MockFactory_Build_Call) Return(_a0 Connection, _a1 error) *MockFactory_Build_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *MockFactory_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Connection) (Connection, error)) *MockFactory_Build_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// Types provides a mock function with no fields
+func (_m *MockFactory) Types() []v0alpha1.ConnectionType {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for Types")
+ }
+
+ var r0 []v0alpha1.ConnectionType
+ if rf, ok := ret.Get(0).(func() []v0alpha1.ConnectionType); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]v0alpha1.ConnectionType)
+ }
+ }
+
+ return r0
+}
+
+// MockFactory_Types_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Types'
+type MockFactory_Types_Call struct {
+ *mock.Call
+}
+
+// Types is a helper method to define mock.On call
+func (_e *MockFactory_Expecter) Types() *MockFactory_Types_Call {
+ return &MockFactory_Types_Call{Call: _e.mock.On("Types")}
+}
+
+func (_c *MockFactory_Types_Call) Run(run func()) *MockFactory_Types_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run()
+ })
+ return _c
+}
+
+func (_c *MockFactory_Types_Call) Return(_a0 []v0alpha1.ConnectionType) *MockFactory_Types_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockFactory_Types_Call) RunAndReturn(run func() []v0alpha1.ConnectionType) *MockFactory_Types_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockFactory creates a new instance of MockFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockFactory(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockFactory {
+ mock := &MockFactory{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/apps/provisioning/pkg/connection/factory_test.go b/apps/provisioning/pkg/connection/factory_test.go
new file mode 100644
index 00000000000..4ce6bc96e0e
--- /dev/null
+++ b/apps/provisioning/pkg/connection/factory_test.go
@@ -0,0 +1,309 @@
+package connection
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestProvideFactory(t *testing.T) {
+ t.Run("should create factory with valid extras", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ extra2 := NewMockExtra(t)
+ extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ provisioning.GitlabConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
+ require.NoError(t, err)
+ require.NotNil(t, factory)
+ })
+
+ t.Run("should create factory with empty extras", func(t *testing.T) {
+ enabled := map[provisioning.ConnectionType]struct{}{}
+
+ factory, err := ProvideFactory(enabled, []Extra{})
+ require.NoError(t, err)
+ require.NotNil(t, factory)
+ })
+
+ t.Run("should create factory with nil enabled map", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ factory, err := ProvideFactory(nil, []Extra{extra1})
+ require.NoError(t, err)
+ require.NotNil(t, factory)
+ })
+
+ t.Run("should return error when duplicate repository types", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ extra2 := NewMockExtra(t)
+ extra2.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
+ require.Error(t, err)
+ assert.Nil(t, factory)
+ assert.Contains(t, err.Error(), "connection type \"github\" is already registered")
+ })
+}
+
+func TestFactory_Types(t *testing.T) {
+ t.Run("should return only enabled types that have extras", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ extra2 := NewMockExtra(t)
+ extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ provisioning.GitlabConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
+ require.NoError(t, err)
+
+ types := factory.Types()
+ assert.Len(t, types, 2)
+ assert.Contains(t, types, provisioning.GithubConnectionType)
+ assert.Contains(t, types, provisioning.GitlabConnectionType)
+ })
+
+ t.Run("should return sorted list of types", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GitlabConnectionType)
+
+ extra2 := NewMockExtra(t)
+ extra2.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ provisioning.GitlabConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
+ require.NoError(t, err)
+
+ types := factory.Types()
+ assert.Len(t, types, 2)
+ // github should come before gitlab alphabetically
+ assert.Equal(t, provisioning.GithubConnectionType, types[0])
+ assert.Equal(t, provisioning.GitlabConnectionType, types[1])
+ })
+
+ t.Run("should return empty list when no types are enabled", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{}
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1})
+ require.NoError(t, err)
+
+ types := factory.Types()
+ assert.Empty(t, types)
+ })
+
+ t.Run("should not return types that are enabled but have no extras", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ provisioning.GitlabConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1})
+ require.NoError(t, err)
+
+ types := factory.Types()
+ assert.Len(t, types, 1)
+ assert.Contains(t, types, provisioning.GithubConnectionType)
+ assert.NotContains(t, types, provisioning.GitlabConnectionType)
+ })
+
+ t.Run("should not return types that have extras but are not enabled", func(t *testing.T) {
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ extra2 := NewMockExtra(t)
+ extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
+ require.NoError(t, err)
+
+ types := factory.Types()
+ assert.Len(t, types, 1)
+ assert.Contains(t, types, provisioning.GithubConnectionType)
+ assert.NotContains(t, types, provisioning.GitlabConnectionType)
+ })
+
+ t.Run("should return empty list when no extras are provided", func(t *testing.T) {
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{})
+ require.NoError(t, err)
+
+ types := factory.Types()
+ assert.Empty(t, types)
+ })
+}
+
+func TestFactory_Build(t *testing.T) {
+ t.Run("should successfully build connection when type is enabled and has extra", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ },
+ }
+
+ mockConnection := NewMockConnection(t)
+ extra := NewMockExtra(t)
+ extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
+ extra.EXPECT().Build(ctx, conn).Return(mockConnection, nil)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra})
+ require.NoError(t, err)
+
+ result, err := factory.Build(ctx, conn)
+ require.NoError(t, err)
+ assert.Equal(t, mockConnection, result)
+ })
+
+ t.Run("should return error when type is not enabled", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GitlabConnectionType,
+ },
+ }
+
+ extra := NewMockExtra(t)
+ extra.EXPECT().Type().Return(provisioning.GitlabConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra})
+ require.NoError(t, err)
+
+ result, err := factory.Build(ctx, conn)
+ require.Error(t, err)
+ assert.Nil(t, result)
+ assert.Contains(t, err.Error(), "connection type \"gitlab\" is not enabled")
+ })
+
+ t.Run("should return error when type is not supported", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GitlabConnectionType,
+ },
+ }
+
+ extra := NewMockExtra(t)
+ extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra})
+ require.NoError(t, err)
+
+ result, err := factory.Build(ctx, conn)
+ require.Error(t, err)
+ assert.Nil(t, result)
+ assert.Contains(t, err.Error(), "connection type \"gitlab\" is not supported")
+ })
+
+ t.Run("should pass through errors from extra.Build()", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ },
+ }
+
+ expectedErr := errors.New("build error")
+ extra := NewMockExtra(t)
+ extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
+ extra.EXPECT().Build(ctx, conn).Return(nil, expectedErr)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra})
+ require.NoError(t, err)
+
+ result, err := factory.Build(ctx, conn)
+ require.Error(t, err)
+ assert.Nil(t, result)
+ assert.Equal(t, expectedErr, err)
+ })
+
+ t.Run("should build with multiple extras registered", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GitlabConnectionType,
+ },
+ }
+
+ mockConnection := NewMockConnection(t)
+
+ extra1 := NewMockExtra(t)
+ extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
+
+ extra2 := NewMockExtra(t)
+ extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
+ extra2.EXPECT().Build(ctx, conn).Return(mockConnection, nil)
+
+ enabled := map[provisioning.ConnectionType]struct{}{
+ provisioning.GithubConnectionType: {},
+ provisioning.GitlabConnectionType: {},
+ }
+
+ factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
+ require.NoError(t, err)
+
+ result, err := factory.Build(ctx, conn)
+ require.NoError(t, err)
+ assert.Equal(t, mockConnection, result)
+ })
+}
diff --git a/apps/provisioning/pkg/connection/github/client.go b/apps/provisioning/pkg/connection/github/client.go
new file mode 100644
index 00000000000..7ddb9a4665e
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/client.go
@@ -0,0 +1,93 @@
+package github
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/google/go-github/v70/github"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+)
+
+// API errors that we need to convey after parsing real GH errors (or faking them).
+var (
+ //lint:ignore ST1005 this is not punctuation
+ ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
+)
+
+//go:generate mockery --name Client --structname MockClient --inpackage --filename client_mock.go --with-expecter
+type Client interface {
+ // Apps and installations
+ GetApp(ctx context.Context) (App, error)
+ GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error)
+}
+
+// App represents a Github App.
+type App struct {
+ // ID represents the GH app ID.
+ ID int64
+ // Slug represents the GH app slug.
+ Slug string
+ // Owner represents the GH account/org owning the app
+ Owner string
+}
+
+// AppInstallation represents a Github App Installation.
+type AppInstallation struct {
+ // ID represents the GH installation ID.
+ ID int64
+ // Whether the installation is enabled or not.
+ Enabled bool
+}
+
+type githubClient struct {
+ gh *github.Client
+}
+
+func NewClient(client *github.Client) Client {
+ return &githubClient{client}
+}
+
+// GetApp gets the app by using the given token.
+func (r *githubClient) GetApp(ctx context.Context) (App, error) {
+ app, _, err := r.gh.Apps.Get(ctx, "")
+ if err != nil {
+ var ghErr *github.ErrorResponse
+ if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
+ return App{}, ErrServiceUnavailable
+ }
+ return App{}, err
+ }
+
+ // TODO(ferruvich): do we need any other info?
+ return App{
+ ID: app.GetID(),
+ Slug: app.GetSlug(),
+ Owner: app.GetOwner().GetLogin(),
+ }, nil
+}
+
+// GetAppInstallation gets the installation of the app related to the given token.
+func (r *githubClient) GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error) {
+ id, err := strconv.Atoi(installationID)
+ if err != nil {
+ return AppInstallation{}, fmt.Errorf("invalid installation ID: %s", installationID)
+ }
+
+ installation, _, err := r.gh.Apps.GetInstallation(ctx, int64(id))
+ if err != nil {
+ var ghErr *github.ErrorResponse
+ if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
+ return AppInstallation{}, ErrServiceUnavailable
+ }
+ return AppInstallation{}, err
+ }
+
+ // TODO(ferruvich): do we need any other info?
+ return AppInstallation{
+ ID: installation.GetID(),
+ Enabled: installation.GetSuspendedAt().IsZero(),
+ }, nil
+}
diff --git a/apps/provisioning/pkg/connection/github/client_mock.go b/apps/provisioning/pkg/connection/github/client_mock.go
new file mode 100644
index 00000000000..c9f009f5021
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/client_mock.go
@@ -0,0 +1,149 @@
+// Code generated by mockery v2.53.4. DO NOT EDIT.
+
+package github
+
+import (
+ context "context"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// MockClient is an autogenerated mock type for the Client type
+type MockClient struct {
+ mock.Mock
+}
+
+type MockClient_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockClient) EXPECT() *MockClient_Expecter {
+ return &MockClient_Expecter{mock: &_m.Mock}
+}
+
+// GetApp provides a mock function with given fields: ctx
+func (_m *MockClient) GetApp(ctx context.Context) (App, error) {
+ ret := _m.Called(ctx)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetApp")
+ }
+
+ var r0 App
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context) (App, error)); ok {
+ return rf(ctx)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context) App); ok {
+ r0 = rf(ctx)
+ } else {
+ r0 = ret.Get(0).(App)
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+ r1 = rf(ctx)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// MockClient_GetApp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetApp'
+type MockClient_GetApp_Call struct {
+ *mock.Call
+}
+
+// GetApp is a helper method to define mock.On call
+// - ctx context.Context
+func (_e *MockClient_Expecter) GetApp(ctx interface{}) *MockClient_GetApp_Call {
+ return &MockClient_GetApp_Call{Call: _e.mock.On("GetApp", ctx)}
+}
+
+func (_c *MockClient_GetApp_Call) Run(run func(ctx context.Context)) *MockClient_GetApp_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context))
+ })
+ return _c
+}
+
+func (_c *MockClient_GetApp_Call) Return(_a0 App, _a1 error) *MockClient_GetApp_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *MockClient_GetApp_Call) RunAndReturn(run func(context.Context) (App, error)) *MockClient_GetApp_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// GetAppInstallation provides a mock function with given fields: ctx, installationID
+func (_m *MockClient) GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error) {
+ ret := _m.Called(ctx, installationID)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetAppInstallation")
+ }
+
+ var r0 AppInstallation
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, string) (AppInstallation, error)); ok {
+ return rf(ctx, installationID)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, string) AppInstallation); ok {
+ r0 = rf(ctx, installationID)
+ } else {
+ r0 = ret.Get(0).(AppInstallation)
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+ r1 = rf(ctx, installationID)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// MockClient_GetAppInstallation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAppInstallation'
+type MockClient_GetAppInstallation_Call struct {
+ *mock.Call
+}
+
+// GetAppInstallation is a helper method to define mock.On call
+// - ctx context.Context
+// - installationID string
+func (_e *MockClient_Expecter) GetAppInstallation(ctx interface{}, installationID interface{}) *MockClient_GetAppInstallation_Call {
+ return &MockClient_GetAppInstallation_Call{Call: _e.mock.On("GetAppInstallation", ctx, installationID)}
+}
+
+func (_c *MockClient_GetAppInstallation_Call) Run(run func(ctx context.Context, installationID string)) *MockClient_GetAppInstallation_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(string))
+ })
+ return _c
+}
+
+func (_c *MockClient_GetAppInstallation_Call) Return(_a0 AppInstallation, _a1 error) *MockClient_GetAppInstallation_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *MockClient_GetAppInstallation_Call) RunAndReturn(run func(context.Context, string) (AppInstallation, error)) *MockClient_GetAppInstallation_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockClient(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockClient {
+ mock := &MockClient{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/apps/provisioning/pkg/connection/github/client_test.go b/apps/provisioning/pkg/connection/github/client_test.go
new file mode 100644
index 00000000000..bae6d6ac1e9
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/client_test.go
@@ -0,0 +1,297 @@
+package github_test
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/google/go-github/v70/github"
+ conngh "github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
+ mockhub "github.com/migueleliasweb/go-github-mock/src/mock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGithubClient_GetApp(t *testing.T) {
+ tests := []struct {
+ name string
+ mockHandler *http.Client
+ token string
+ wantApp conngh.App
+ wantErr error
+ }{
+ {
+ name: "get app successfully",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetApp,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ app := &github.App{
+ ID: github.Ptr(int64(12345)),
+ Slug: github.Ptr("my-test-app"),
+ Owner: &github.User{
+ Login: github.Ptr("grafana"),
+ },
+ }
+ w.WriteHeader(http.StatusOK)
+ require.NoError(t, json.NewEncoder(w).Encode(app))
+ }),
+ ),
+ ),
+ token: "test-token",
+ wantApp: conngh.App{
+ ID: 12345,
+ Slug: "my-test-app",
+ Owner: "grafana",
+ },
+ wantErr: nil,
+ },
+ {
+ name: "service unavailable",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetApp,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusServiceUnavailable,
+ },
+ Message: "Service unavailable",
+ }))
+ }),
+ ),
+ ),
+ token: "test-token",
+ wantApp: conngh.App{},
+ wantErr: conngh.ErrServiceUnavailable,
+ },
+ {
+ name: "other error",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetApp,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusInternalServerError)
+ require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusInternalServerError,
+ },
+ Message: "Internal server error",
+ }))
+ }),
+ ),
+ ),
+ token: "test-token",
+ wantApp: conngh.App{},
+ wantErr: &github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusInternalServerError,
+ },
+ Message: "Internal server error",
+ },
+ },
+ {
+ name: "unauthorized error",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetApp,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusUnauthorized)
+ require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusUnauthorized,
+ },
+ Message: "Bad credentials",
+ }))
+ }),
+ ),
+ ),
+ token: "invalid-token",
+ wantApp: conngh.App{},
+ wantErr: &github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusUnauthorized,
+ },
+ Message: "Bad credentials",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Create a mock client
+ ghClient := github.NewClient(tt.mockHandler)
+ client := conngh.NewClient(ghClient)
+
+ // Call the method being tested
+ app, err := client.GetApp(context.Background())
+
+ // Check the error
+ if tt.wantErr != nil {
+ assert.Error(t, err)
+ assert.Equal(t, tt.wantApp, app)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tt.wantApp, app)
+ }
+ })
+ }
+}
+
+func TestGithubClient_GetAppInstallation(t *testing.T) {
+ tests := []struct {
+ name string
+ mockHandler *http.Client
+ appToken string
+ installationID string
+ wantInstallation conngh.AppInstallation
+ wantErr bool
+ errContains string
+ }{
+ {
+ name: "get disabled app installation successfully",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetAppInstallationsByInstallationId,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ installation := &github.Installation{
+ ID: github.Ptr(int64(67890)),
+ SuspendedAt: github.Ptr(github.Timestamp{Time: time.Now()}),
+ }
+ w.WriteHeader(http.StatusOK)
+ require.NoError(t, json.NewEncoder(w).Encode(installation))
+ }),
+ ),
+ ),
+ appToken: "test-app-token",
+ installationID: "67890",
+ wantInstallation: conngh.AppInstallation{
+ ID: 67890,
+ Enabled: false,
+ },
+ wantErr: false,
+ },
+ {
+ name: "get enabled app installation successfully",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetAppInstallationsByInstallationId,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ installation := &github.Installation{
+ ID: github.Ptr(int64(67890)),
+ SuspendedAt: nil,
+ }
+ w.WriteHeader(http.StatusOK)
+ require.NoError(t, json.NewEncoder(w).Encode(installation))
+ }),
+ ),
+ ),
+ appToken: "test-app-token",
+ installationID: "67890",
+ wantInstallation: conngh.AppInstallation{
+ ID: 67890,
+ Enabled: true,
+ },
+ wantErr: false,
+ },
+ {
+ name: "invalid installation ID",
+ mockHandler: mockhub.NewMockedHTTPClient(),
+ appToken: "test-app-token",
+ installationID: "not-a-number",
+ wantInstallation: conngh.AppInstallation{},
+ wantErr: true,
+ errContains: "invalid installation ID",
+ },
+ {
+ name: "service unavailable",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetAppInstallationsByInstallationId,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusServiceUnavailable,
+ },
+ Message: "Service unavailable",
+ }))
+ }),
+ ),
+ ),
+ appToken: "test-app-token",
+ installationID: "67890",
+ wantInstallation: conngh.AppInstallation{},
+ wantErr: true,
+ },
+ {
+ name: "installation not found",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetAppInstallationsByInstallationId,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusNotFound,
+ },
+ Message: "Not Found",
+ }))
+ }),
+ ),
+ ),
+ appToken: "test-app-token",
+ installationID: "99999",
+ wantInstallation: conngh.AppInstallation{},
+ wantErr: true,
+ },
+ {
+ name: "other error",
+ mockHandler: mockhub.NewMockedHTTPClient(
+ mockhub.WithRequestMatchHandler(
+ mockhub.GetAppInstallationsByInstallationId,
+ http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusInternalServerError)
+ require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
+ Response: &http.Response{
+ StatusCode: http.StatusInternalServerError,
+ },
+ Message: "Internal server error",
+ }))
+ }),
+ ),
+ ),
+ appToken: "test-app-token",
+ installationID: "67890",
+ wantInstallation: conngh.AppInstallation{},
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Create a mock client
+ ghClient := github.NewClient(tt.mockHandler)
+ client := conngh.NewClient(ghClient)
+
+ // Call the method being tested
+ installation, err := client.GetAppInstallation(context.Background(), tt.installationID)
+
+ // Check the error
+ if tt.wantErr {
+ assert.Error(t, err)
+ if tt.errContains != "" {
+ assert.Contains(t, err.Error(), tt.errContains)
+ }
+ } else {
+ assert.NoError(t, err)
+ }
+
+ // Check the result
+ assert.Equal(t, tt.wantInstallation, installation)
+ })
+ }
+}
diff --git a/apps/provisioning/pkg/connection/github/connection.go b/apps/provisioning/pkg/connection/github/connection.go
new file mode 100644
index 00000000000..6a2da98ac8d
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/connection.go
@@ -0,0 +1,192 @@
+package github
+
+import (
+ "context"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/golang-jwt/jwt/v4"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ "github.com/grafana/grafana/apps/provisioning/pkg/connection"
+ common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/util/validation/field"
+)
+
+//go:generate mockery --name GithubFactory --structname MockGithubFactory --inpackage --filename factory_mock.go --with-expecter
+type GithubFactory interface {
+ New(ctx context.Context, ghToken common.RawSecureValue) Client
+}
+
+type Connection struct {
+ obj *provisioning.Connection
+ ghFactory GithubFactory
+}
+
+func NewConnection(
+ obj *provisioning.Connection,
+ factory GithubFactory,
+) Connection {
+ return Connection{
+ obj: obj,
+ ghFactory: factory,
+ }
+}
+
+const (
+ //TODO(ferruvich): these probably need to be setup in API configuration.
+ githubInstallationURL = "https://github.com/settings/installations"
+ jwtExpirationMinutes = 10 // GitHub Apps JWT tokens expire in 10 minutes maximum
+)
+
+// Mutate performs in place mutation of the underneath resource.
+func (c *Connection) Mutate(_ context.Context) error {
+ // Do nothing in case spec.Github is nil.
+ // If this field is required, we should fail at validation time.
+ if c.obj.Spec.GitHub == nil {
+ return nil
+ }
+
+ c.obj.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, c.obj.Spec.GitHub.InstallationID)
+
+ // Generate JWT token if private key is being provided.
+ // Same as for the spec.Github, if such a field is required, Validation will take care of that.
+ if !c.obj.Secure.PrivateKey.Create.IsZero() {
+ token, err := generateToken(c.obj.Spec.GitHub.AppID, c.obj.Secure.PrivateKey.Create)
+ if err != nil {
+ return fmt.Errorf("failed to generate JWT token: %w", err)
+ }
+
+ // Store the generated token
+ c.obj.Secure.Token = common.InlineSecureValue{Create: token}
+ }
+
+ return nil
+}
+
+// Token generates and returns the Connection token.
+func generateToken(appID string, privateKey common.RawSecureValue) (common.RawSecureValue, error) {
+ // Decode base64-encoded private key
+ privateKeyPEM, err := base64.StdEncoding.DecodeString(string(privateKey))
+ if err != nil {
+ return "", fmt.Errorf("failed to decode base64 private key: %w", err)
+ }
+
+ // Parse the private key
+ key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse private key: %w", err)
+ }
+
+ // Create the JWT token
+ now := time.Now()
+ claims := jwt.RegisteredClaims{
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(jwtExpirationMinutes) * time.Minute)),
+ Issuer: appID,
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
+ signedToken, err := token.SignedString(key)
+ if err != nil {
+ return "", fmt.Errorf("failed to sign JWT token: %w", err)
+ }
+
+ return common.RawSecureValue(signedToken), nil
+}
+
+// Validate ensures the resource _looks_ correct.
+func (c *Connection) Validate(ctx context.Context) error {
+ list := field.ErrorList{}
+
+ if c.obj.Spec.Type != provisioning.GithubConnectionType {
+ list = append(list, field.Invalid(field.NewPath("spec", "type"), c.obj.Spec.Type, "invalid connection type"))
+
+ // Doesn't make much sense to continue validating a connection which is not a Github one.
+ return toError(c.obj.GetName(), list)
+ }
+
+ if c.obj.Spec.GitHub == nil {
+ list = append(
+ list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
+ )
+
+ // Doesn't make much sense to continue validating a connection with no information.
+ return toError(c.obj.GetName(), list)
+ }
+
+ if c.obj.Secure.PrivateKey.IsZero() {
+ list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
+ }
+ if c.obj.Secure.Token.IsZero() {
+ list = append(list, field.Required(field.NewPath("secure", "token"), "token must be specified for GitHub connection"))
+ }
+ if !c.obj.Secure.ClientSecret.IsZero() {
+ list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
+ }
+
+ // Validate GitHub configuration fields
+ if c.obj.Spec.GitHub.AppID == "" {
+ list = append(list, field.Required(field.NewPath("spec", "github", "appID"), "appID must be specified for GitHub connection"))
+ }
+ if c.obj.Spec.GitHub.InstallationID == "" {
+ list = append(list, field.Required(field.NewPath("spec", "github", "installationID"), "installationID must be specified for GitHub connection"))
+ }
+
+ // In case we have any error above, we don't go forward with the validation, and return the errors.
+ if len(list) > 0 {
+ return toError(c.obj.GetName(), list)
+ }
+
+ // Validating app content via GH API
+ if err := c.validateAppAndInstallation(ctx); err != nil {
+ list = append(list, err)
+ }
+
+ return toError(c.obj.GetName(), list)
+}
+
+// validateAppAndInstallation validates the appID and installationID against the given github token.
+func (c *Connection) validateAppAndInstallation(ctx context.Context) *field.Error {
+ ghClient := c.ghFactory.New(ctx, c.obj.Secure.Token.Create)
+
+ app, err := ghClient.GetApp(ctx)
+ if err != nil {
+ if errors.Is(err, ErrServiceUnavailable) {
+ return field.InternalError(field.NewPath("spec", "token"), ErrServiceUnavailable)
+ }
+ return field.Invalid(field.NewPath("spec", "token"), "[REDACTED]", "invalid token")
+ }
+
+ if fmt.Sprintf("%d", app.ID) != c.obj.Spec.GitHub.AppID {
+ return field.Invalid(field.NewPath("spec", "appID"), c.obj.Spec.GitHub.AppID, "appID mismatch")
+ }
+
+ _, err = ghClient.GetAppInstallation(ctx, c.obj.Spec.GitHub.InstallationID)
+ if err != nil {
+ if errors.Is(err, ErrServiceUnavailable) {
+ return field.InternalError(field.NewPath("spec", "token"), ErrServiceUnavailable)
+ }
+ return field.Invalid(field.NewPath("spec", "installationID"), c.obj.Spec.GitHub.InstallationID, "invalid installation ID")
+ }
+
+ return nil
+}
+
+// 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.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
+ name,
+ list,
+ )
+}
+
+var (
+ _ connection.Connection = (*Connection)(nil)
+)
diff --git a/apps/provisioning/pkg/connection/github/connection_test.go b/apps/provisioning/pkg/connection/github/connection_test.go
new file mode 100644
index 00000000000..6a916db730e
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/connection_test.go
@@ -0,0 +1,434 @@
+package github
+
+import (
+ "context"
+ "encoding/base64"
+ "testing"
+
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+//nolint:gosec // Test RSA private key (generated for testing purposes only)
+const testPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAoInVbLY9io2Q/wHvUIXlEHg2Qyvd8eRzBAVEJ92DS6fx9H10
+06V0VRm78S0MXyo6i+n8ZAbZ0/R+GWpP2Ephxm0Gs2zo+iO2mpB19xQFI4o6ZTOw
+b2WyjSaa2Vr4oyDkqti6AvfjW4VUAu932e08GkgwmmQSHXj7FX2CMWjgUwTTcuaX
+65SHNKLNYLUP0HTumLzoZeqDTdoMMpKNdgH9Avr4/8vkVJ0mD6rqvxnw3JHsseNO
+WdQTxf2aApBNHIIKxWZ2i/ZmjLNey7kltgjEquGiBdJvip3fHhH5XHdkrXcjRtnw
+OJDnDmi5lQwv5yUBOSkbvbXRv/L/m0YLoD/fbwIDAQABAoIBAFfl//hM8/cnuesV
++R1Con/ZAgTXQOdPqPXbmEyniVrkMqMmCdBUOBTcST4s5yg36+RtkeaGpb/ajyyF
+PAB2AYDucwvMpudGpJWOYTiOOp4R8hU1LvZfXVrRd1lo6NgQi4NLtNUpOtACeVQ+
+H4Yv0YemXQ47mnuOoRNMK/u3q5NoIdSahWptXBgUno8KklNpUrH3IYWaUxfBzDN3
+2xsVRTn2SfTSyoDmTDdTgptJONmoK1/sV7UsgWksdFc6XyYhsFAZgOGEJrBABRvF
+546dyQ0cWxuPyVXpM7CN3tqC5ssvLjElg3LicK1V6gnjpdRnnvX88d1Eh3Uc/9IM
+OZInT2ECgYEA6W8sQXTWinyEwl8SDKKMbB2ApIghAcFgdRxprZE4WFxjsYNCNL70
+dnSB7MRuzmxf5W77cV0N7JhH66N8HvY6Xq9olrpQ5dNttR4w8Pyv3wavDe8x7seL
+5L2Xtbu7ihDr8Dk27MjiBSin3IxhBP5CJS910+pR6LrAWtEuU+FzFfECgYEAsA6y
+qxHhCMXlTnauXhsnmPd1g61q7chW8kLQFYtHMLlQlgjHTW7irDZ9cPbPYDNjwRLO
+7KLorcpv2NKe7rqq2ZyCm6hf1b9WnlQjo3dLpNWMu6fhy/smK8MgbRqcWpX+oTKF
+79mK6hbY7o6eBzsQHBl7Z+LBNuwYmp9qOodPa18CgYEArv6ipKdcNhFGzRfMRiCN
+OHederp6VACNuP2F05IsNUF9kxOdTEFirnKE++P+VU01TqA2azOhPp6iO+ohIGzi
+MR06QNSH1OL9OWvasK4dggpWrRGF00VQgDgJRTnpS4WH+lxJ6pRlrAxgWpv6F24s
+VAgSQr1Ejj2B+hMasdMvHWECgYBJ4uE4yhgXBnZlp4kmFV9Y4wF+cZkekaVrpn6N
+jBYkbKFVVfnOlWqru3KJpgsB5I9IyAvvY68iwIKQDFSG+/AXw4dMrC0MF3DSoZ0T
+TU2Br92QI7SvVod+djV1lGVp3ukt3XY4YqPZ+hywgUnw3uiz4j3YK2HLGup4ec6r
+IX5DIQKBgHRLzvT3zqtlR1Oh0vv098clLwt+pGzXOxzJpxioOa5UqK13xIpFXbcg
+iWUVh5YXCcuqaICUv4RLIEac5xQitk9Is/9IhP0NJ/81rHniosvdSpCeFXzxTImS
+B8Uc0WUgheB4+yVKGnYpYaSOgFFI5+1BYUva/wDHLy2pWHz39Usb
+-----END RSA PRIVATE KEY-----`
+
+func TestConnection_Mutate(t *testing.T) {
+ t.Run("should add URL to Github connection", func(t *testing.T) {
+ c := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Name: "test-private-key",
+ },
+ },
+ }
+
+ mockFactory := NewMockGithubFactory(t)
+ conn := NewConnection(c, mockFactory)
+
+ require.NoError(t, conn.Mutate(context.Background()))
+ assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
+ })
+
+ t.Run("should generate JWT token when private key is provided", func(t *testing.T) {
+ privateKeyBase64 := base64.StdEncoding.EncodeToString([]byte(testPrivateKeyPEM))
+
+ c := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue(privateKeyBase64),
+ },
+ },
+ }
+
+ mockFactory := NewMockGithubFactory(t)
+ conn := NewConnection(c, mockFactory)
+
+ require.NoError(t, conn.Mutate(context.Background()))
+ assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
+ assert.False(t, c.Secure.Token.Create.IsZero(), "JWT token should be generated")
+ })
+
+ t.Run("should do nothing when GitHub config is nil", func(t *testing.T) {
+ c := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GitlabConnectionType,
+ Gitlab: &provisioning.GitlabConnectionConfig{
+ ClientID: "clientID",
+ },
+ },
+ }
+
+ mockFactory := NewMockGithubFactory(t)
+ conn := NewConnection(c, mockFactory)
+
+ require.NoError(t, conn.Mutate(context.Background()))
+ })
+
+ t.Run("should fail when private key is not base64", func(t *testing.T) {
+ c := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("invalid-key"),
+ },
+ },
+ }
+
+ mockFactory := NewMockGithubFactory(t)
+ conn := NewConnection(c, mockFactory)
+
+ err := conn.Mutate(context.Background())
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "failed to generate JWT token")
+ assert.Contains(t, err.Error(), "failed to decode base64 private key")
+ })
+
+ t.Run("should fail when private key is invalid", func(t *testing.T) {
+ c := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue(base64.StdEncoding.EncodeToString([]byte("invalid-key"))),
+ },
+ },
+ }
+
+ mockFactory := NewMockGithubFactory(t)
+ conn := NewConnection(c, mockFactory)
+
+ err := conn.Mutate(context.Background())
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "failed to generate JWT token")
+ assert.Contains(t, err.Error(), "failed to parse private key")
+ })
+}
+
+func TestConnection_Validate(t *testing.T) {
+ tests := []struct {
+ name string
+ connection *provisioning.Connection
+ setupMock func(*MockGithubFactory)
+ wantErr bool
+ errMsgContains []string
+ }{
+ {
+ name: "invalid type returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: "invalid",
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.type"},
+ },
+ {
+ name: "github type without github config returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.github"},
+ },
+ {
+ name: "github type without private key returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"secure.privateKey"},
+ },
+ {
+ name: "github type without token returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"secure.token"},
+ },
+ {
+ name: "github type with client secret returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ ClientSecret: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-client-secret"),
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"secure.clientSecret"},
+ },
+ {
+ name: "github type without appID returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.github.appID"},
+ },
+ {
+ name: "github type without installationID returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Name: "test-private-key",
+ },
+ Token: common.InlineSecureValue{
+ Name: "test-token",
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.github.installationID"},
+ },
+ {
+ name: "github type with valid config is valid",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ wantErr: false,
+ setupMock: func(mockFactory *MockGithubFactory) {
+ mockClient := NewMockClient(t)
+
+ mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
+ mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 123, Slug: "test-app"}, nil)
+ mockClient.EXPECT().GetAppInstallation(mock.Anything, "456").Return(AppInstallation{ID: 456}, nil)
+ },
+ },
+ {
+ name: "problem getting app returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.token", "[REDACTED]"},
+ setupMock: func(mockFactory *MockGithubFactory) {
+ mockClient := NewMockClient(t)
+
+ mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
+ mockClient.EXPECT().GetApp(mock.Anything).Return(App{}, assert.AnError)
+ },
+ },
+ {
+ name: "mismatched app ID returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.appID"},
+ setupMock: func(mockFactory *MockGithubFactory) {
+ mockClient := NewMockClient(t)
+
+ mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
+ mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 444, Slug: "test-app"}, nil)
+ },
+ },
+ {
+ name: "problem when getting installation returns error",
+ connection: &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ wantErr: true,
+ errMsgContains: []string{"spec.installationID", "456"},
+ setupMock: func(mockFactory *MockGithubFactory) {
+ mockClient := NewMockClient(t)
+
+ mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
+ mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 123, Slug: "test-app"}, nil)
+ mockClient.EXPECT().GetAppInstallation(mock.Anything, "456").Return(AppInstallation{}, assert.AnError)
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ mockFactory := NewMockGithubFactory(t)
+ if tt.setupMock != nil {
+ tt.setupMock(mockFactory)
+ }
+
+ conn := NewConnection(tt.connection, mockFactory)
+ err := conn.Validate(context.Background())
+ if tt.wantErr {
+ assert.Error(t, err)
+ for _, msg := range tt.errMsgContains {
+ assert.Contains(t, err.Error(), msg)
+ }
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/apps/provisioning/pkg/connection/github/extra.go b/apps/provisioning/pkg/connection/github/extra.go
new file mode 100644
index 00000000000..2c207637c61
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/extra.go
@@ -0,0 +1,36 @@
+package github
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ "github.com/grafana/grafana/apps/provisioning/pkg/connection"
+)
+
+type extra struct {
+ factory GithubFactory
+}
+
+func (e *extra) Type() provisioning.ConnectionType {
+ return provisioning.GithubConnectionType
+}
+
+func (e *extra) Build(ctx context.Context, connection *provisioning.Connection) (connection.Connection, error) {
+ logger := logging.FromContext(ctx)
+ if connection == nil || connection.Spec.GitHub == nil {
+ logger.Error("connection is nil or github info is nil")
+
+ return nil, fmt.Errorf("invalid github connection")
+ }
+
+ c := NewConnection(connection, e.factory)
+ return &c, nil
+}
+
+func Extra(factory GithubFactory) connection.Extra {
+ return &extra{
+ factory: factory,
+ }
+}
diff --git a/apps/provisioning/pkg/connection/github/extra_test.go b/apps/provisioning/pkg/connection/github/extra_test.go
new file mode 100644
index 00000000000..c5bcc8279d9
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/extra_test.go
@@ -0,0 +1,126 @@
+package github_test
+
+import (
+ "context"
+ "testing"
+
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ "github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
+ common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestExtra_Type(t *testing.T) {
+ t.Run("should return GithubConnectionType", func(t *testing.T) {
+ mockFactory := github.NewMockGithubFactory(t)
+ e := github.Extra(mockFactory)
+ result := e.Type()
+ assert.Equal(t, provisioning.GithubConnectionType, result)
+ })
+}
+
+func TestExtra_Build(t *testing.T) {
+ t.Run("should successfully build connection", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-private-key"),
+ },
+ },
+ }
+
+ mockFactory := github.NewMockGithubFactory(t)
+
+ e := github.Extra(mockFactory)
+
+ result, err := e.Build(ctx, conn)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ })
+
+ t.Run("should handle different connection configurations", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "another-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "789",
+ InstallationID: "101112",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ PrivateKey: common.InlineSecureValue{
+ Name: "existing-private-key",
+ },
+ Token: common.InlineSecureValue{
+ Name: "existing-token",
+ },
+ },
+ }
+
+ mockFactory := github.NewMockGithubFactory(t)
+
+ e := github.Extra(mockFactory)
+
+ result, err := e.Build(ctx, conn)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ })
+
+ t.Run("should build connection with background context", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ }
+
+ mockFactory := github.NewMockGithubFactory(t)
+ e := github.Extra(mockFactory)
+ result, err := e.Build(ctx, conn)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ })
+
+ t.Run("should always pass empty token to factory.New", func(t *testing.T) {
+ ctx := context.Background()
+ conn := &provisioning.Connection{
+ ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
+ Spec: provisioning.ConnectionSpec{
+ Type: provisioning.GithubConnectionType,
+ GitHub: &provisioning.GitHubConnectionConfig{
+ AppID: "123",
+ InstallationID: "456",
+ },
+ },
+ Secure: provisioning.ConnectionSecure{
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("some-token"),
+ },
+ },
+ }
+
+ mockFactory := github.NewMockGithubFactory(t)
+ e := github.Extra(mockFactory)
+ result, err := e.Build(ctx, conn)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ })
+}
diff --git a/apps/provisioning/pkg/connection/github/factory.go b/apps/provisioning/pkg/connection/github/factory.go
new file mode 100644
index 00000000000..2399f3c9f69
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/factory.go
@@ -0,0 +1,39 @@
+package github
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/google/go-github/v70/github"
+ "golang.org/x/oauth2"
+
+ common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
+)
+
+// Factory creates new GitHub clients.
+// It exists only for the ability to test the code easily.
+type Factory struct {
+ // Client allows overriding the client to use in the GH client returned. It exists primarily for testing.
+ // FIXME: we should replace in this way. We should add some options pattern for the factory.
+ Client *http.Client
+}
+
+func ProvideFactory() GithubFactory {
+ return &Factory{}
+}
+
+func (r *Factory) New(ctx context.Context, ghToken common.RawSecureValue) Client {
+ if r.Client != nil {
+ return NewClient(github.NewClient(r.Client))
+ }
+
+ if !ghToken.IsZero() {
+ tokenSrc := oauth2.StaticTokenSource(
+ &oauth2.Token{AccessToken: string(ghToken)},
+ )
+ tokenClient := oauth2.NewClient(ctx, tokenSrc)
+ return NewClient(github.NewClient(tokenClient))
+ }
+
+ return NewClient(github.NewClient(&http.Client{}))
+}
diff --git a/apps/provisioning/pkg/connection/github/factory_mock.go b/apps/provisioning/pkg/connection/github/factory_mock.go
new file mode 100644
index 00000000000..a9e1424b62d
--- /dev/null
+++ b/apps/provisioning/pkg/connection/github/factory_mock.go
@@ -0,0 +1,86 @@
+// Code generated by mockery v2.53.4. DO NOT EDIT.
+
+package github
+
+import (
+ context "context"
+
+ v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// MockGithubFactory is an autogenerated mock type for the GithubFactory type
+type MockGithubFactory struct {
+ mock.Mock
+}
+
+type MockGithubFactory_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockGithubFactory) EXPECT() *MockGithubFactory_Expecter {
+ return &MockGithubFactory_Expecter{mock: &_m.Mock}
+}
+
+// New provides a mock function with given fields: ctx, ghToken
+func (_m *MockGithubFactory) New(ctx context.Context, ghToken v0alpha1.RawSecureValue) Client {
+ ret := _m.Called(ctx, ghToken)
+
+ if len(ret) == 0 {
+ panic("no return value specified for New")
+ }
+
+ var r0 Client
+ if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.RawSecureValue) Client); ok {
+ r0 = rf(ctx, ghToken)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(Client)
+ }
+ }
+
+ return r0
+}
+
+// MockGithubFactory_New_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'New'
+type MockGithubFactory_New_Call struct {
+ *mock.Call
+}
+
+// New is a helper method to define mock.On call
+// - ctx context.Context
+// - ghToken v0alpha1.RawSecureValue
+func (_e *MockGithubFactory_Expecter) New(ctx interface{}, ghToken interface{}) *MockGithubFactory_New_Call {
+ return &MockGithubFactory_New_Call{Call: _e.mock.On("New", ctx, ghToken)}
+}
+
+func (_c *MockGithubFactory_New_Call) Run(run func(ctx context.Context, ghToken v0alpha1.RawSecureValue)) *MockGithubFactory_New_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(v0alpha1.RawSecureValue))
+ })
+ return _c
+}
+
+func (_c *MockGithubFactory_New_Call) Return(_a0 Client) *MockGithubFactory_New_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockGithubFactory_New_Call) RunAndReturn(run func(context.Context, v0alpha1.RawSecureValue) Client) *MockGithubFactory_New_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockGithubFactory creates a new instance of MockGithubFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockGithubFactory(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockGithubFactory {
+ mock := &MockGithubFactory{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/apps/provisioning/pkg/connection/mutator.go b/apps/provisioning/pkg/connection/mutator.go
deleted file mode 100644
index 30291669905..00000000000
--- a/apps/provisioning/pkg/connection/mutator.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package connection
-
-import (
- "fmt"
-
- provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
-)
-
-const (
- githubInstallationURL = "https://github.com/settings/installations"
-)
-
-func MutateConnection(connection *provisioning.Connection) error {
- switch connection.Spec.Type {
- case provisioning.GithubConnectionType:
- // Do nothing in case spec.Github is nil.
- // If this field is required, we should fail at validation time.
- if connection.Spec.GitHub == nil {
- return nil
- }
-
- connection.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, connection.Spec.GitHub.InstallationID)
- return nil
- default:
- // TODO: we need to setup the URL for bitbucket and gitlab.
- return nil
- }
-}
diff --git a/apps/provisioning/pkg/connection/mutator_test.go b/apps/provisioning/pkg/connection/mutator_test.go
deleted file mode 100644
index a25aabd10a1..00000000000
--- a/apps/provisioning/pkg/connection/mutator_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package connection_test
-
-import (
- "testing"
-
- provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
- "github.com/grafana/grafana/apps/provisioning/pkg/connection"
- common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-func TestMutateConnection(t *testing.T) {
- t.Run("should add URL to Github connection", func(t *testing.T) {
- c := &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GithubConnectionType,
- GitHub: &provisioning.GitHubConnectionConfig{
- AppID: "123",
- InstallationID: "456",
- },
- },
- Secure: provisioning.ConnectionSecure{
- PrivateKey: common.InlineSecureValue{
- Name: "test-private-key",
- },
- },
- }
-
- require.NoError(t, connection.MutateConnection(c))
- assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
- })
-}
diff --git a/apps/provisioning/pkg/connection/validator.go b/apps/provisioning/pkg/connection/validator.go
deleted file mode 100644
index c2537e3af2f..00000000000
--- a/apps/provisioning/pkg/connection/validator.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package connection
-
-import (
- provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/util/validation/field"
-)
-
-func ValidateConnection(connection *provisioning.Connection) error {
- list := field.ErrorList{}
-
- if connection.Spec.Type == "" {
- list = append(list, field.Required(field.NewPath("spec", "type"), "type must be specified"))
- }
-
- switch connection.Spec.Type {
- case provisioning.GithubConnectionType:
- list = append(list, validateGithubConnection(connection)...)
- case provisioning.BitbucketConnectionType:
- list = append(list, validateBitbucketConnection(connection)...)
- case provisioning.GitlabConnectionType:
- list = append(list, validateGitlabConnection(connection)...)
- default:
- list = append(
- list, field.NotSupported(
- field.NewPath("spec", "type"),
- connection.Spec.Type,
- []provisioning.ConnectionType{
- provisioning.GithubConnectionType,
- provisioning.BitbucketConnectionType,
- provisioning.GitlabConnectionType,
- }),
- )
- }
-
- return toError(connection.GetName(), list)
-}
-
-func validateGithubConnection(connection *provisioning.Connection) field.ErrorList {
- list := field.ErrorList{}
-
- if connection.Spec.GitHub == nil {
- list = append(
- list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
- )
- }
-
- if connection.Secure.PrivateKey.IsZero() {
- list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
- }
- if !connection.Secure.ClientSecret.IsZero() {
- list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
- }
-
- return list
-}
-
-func validateBitbucketConnection(connection *provisioning.Connection) field.ErrorList {
- list := field.ErrorList{}
-
- if connection.Spec.Bitbucket == nil {
- list = append(
- list, field.Required(field.NewPath("spec", "bitbucket"), "bitbucket info must be specified in Bitbucket connection"),
- )
- }
- if connection.Secure.ClientSecret.IsZero() {
- list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Bitbucket connection"))
- }
- if !connection.Secure.PrivateKey.IsZero() {
- list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Bitbucket connection"))
- }
-
- return list
-}
-
-func validateGitlabConnection(connection *provisioning.Connection) field.ErrorList {
- list := field.ErrorList{}
-
- if connection.Spec.Gitlab == nil {
- list = append(
- list, field.Required(field.NewPath("spec", "gitlab"), "gitlab info must be specified in Gitlab connection"),
- )
- }
- if connection.Secure.ClientSecret.IsZero() {
- list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Gitlab connection"))
- }
- if !connection.Secure.PrivateKey.IsZero() {
- list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Gitlab connection"))
- }
-
- return 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.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
- name,
- list,
- )
-}
diff --git a/apps/provisioning/pkg/connection/validator_test.go b/apps/provisioning/pkg/connection/validator_test.go
deleted file mode 100644
index 23d4b01b800..00000000000
--- a/apps/provisioning/pkg/connection/validator_test.go
+++ /dev/null
@@ -1,253 +0,0 @@
-package connection_test
-
-import (
- "testing"
-
- provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
- "github.com/grafana/grafana/apps/provisioning/pkg/connection"
- common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
- "github.com/stretchr/testify/assert"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-func TestValidateConnection(t *testing.T) {
- tests := []struct {
- name string
- connection *provisioning.Connection
- wantErr bool
- errMsg string
- }{
- {
- name: "empty type returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{},
- },
- wantErr: true,
- errMsg: "spec.type",
- },
- {
- name: "invalid type returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: "invalid",
- },
- },
- wantErr: true,
- errMsg: "spec.type",
- },
- {
- name: "github type without github config returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GithubConnectionType,
- },
- },
- wantErr: true,
- errMsg: "spec.github",
- },
- {
- name: "github type without private key returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GithubConnectionType,
- GitHub: &provisioning.GitHubConnectionConfig{
- AppID: "123",
- InstallationID: "456",
- },
- },
- },
- wantErr: true,
- errMsg: "secure.privateKey",
- },
- {
- name: "github type with client secret returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GithubConnectionType,
- GitHub: &provisioning.GitHubConnectionConfig{
- AppID: "123",
- InstallationID: "456",
- },
- },
- Secure: provisioning.ConnectionSecure{
- PrivateKey: common.InlineSecureValue{
- Name: "test-private-key",
- },
- ClientSecret: common.InlineSecureValue{
- Name: "test-client-secret",
- },
- },
- },
- wantErr: true,
- errMsg: "secure.clientSecret",
- },
- {
- name: "github type with github config is valid",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GithubConnectionType,
- GitHub: &provisioning.GitHubConnectionConfig{
- AppID: "123",
- InstallationID: "456",
- },
- },
- Secure: provisioning.ConnectionSecure{
- PrivateKey: common.InlineSecureValue{
- Name: "test-private-key",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "bitbucket type without bitbucket config returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.BitbucketConnectionType,
- },
- },
- wantErr: true,
- errMsg: "spec.bitbucket",
- },
- {
- name: "bitbucket type without client secret returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.BitbucketConnectionType,
- Bitbucket: &provisioning.BitbucketConnectionConfig{
- ClientID: "client-123",
- },
- },
- },
- wantErr: true,
- errMsg: "secure.clientSecret",
- },
- {
- name: "bitbucket type with private key returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.BitbucketConnectionType,
- Bitbucket: &provisioning.BitbucketConnectionConfig{
- ClientID: "client-123",
- },
- },
- Secure: provisioning.ConnectionSecure{
- PrivateKey: common.InlineSecureValue{
- Name: "test-private-key",
- },
- ClientSecret: common.InlineSecureValue{
- Name: "test-client-secret",
- },
- },
- },
- wantErr: true,
- errMsg: "secure.privateKey",
- },
- {
- name: "bitbucket type with bitbucket config is valid",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.BitbucketConnectionType,
- Bitbucket: &provisioning.BitbucketConnectionConfig{
- ClientID: "client-123",
- },
- },
- Secure: provisioning.ConnectionSecure{
- ClientSecret: common.InlineSecureValue{
- Name: "test-client-secret",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "gitlab type without gitlab config returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GitlabConnectionType,
- },
- },
- wantErr: true,
- errMsg: "spec.gitlab",
- },
- {
- name: "gitlab type without client secret returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GitlabConnectionType,
- Gitlab: &provisioning.GitlabConnectionConfig{
- ClientID: "client-456",
- },
- },
- },
- wantErr: true,
- errMsg: "secure.clientSecret",
- },
- {
- name: "gitlab type with private key returns error",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GitlabConnectionType,
- Gitlab: &provisioning.GitlabConnectionConfig{
- ClientID: "client-456",
- },
- },
- Secure: provisioning.ConnectionSecure{
- PrivateKey: common.InlineSecureValue{
- Name: "test-private-key",
- },
- ClientSecret: common.InlineSecureValue{
- Name: "test-client-secret",
- },
- },
- },
- wantErr: true,
- errMsg: "secure.privateKey",
- },
- {
- name: "gitlab type with gitlab config is valid",
- connection: &provisioning.Connection{
- ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
- Spec: provisioning.ConnectionSpec{
- Type: provisioning.GitlabConnectionType,
- Gitlab: &provisioning.GitlabConnectionConfig{
- ClientID: "client-456",
- },
- },
- Secure: provisioning.ConnectionSecure{
- ClientSecret: common.InlineSecureValue{
- Name: "test-client-secret",
- },
- },
- },
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := connection.ValidateConnection(tt.connection)
- if tt.wantErr {
- assert.Error(t, err)
- if tt.errMsg != "" {
- assert.Contains(t, err.Error(), tt.errMsg)
- }
- } else {
- assert.NoError(t, err)
- }
- })
- }
-}
diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go
index 8ac26b192c9..f5be635560d 100644
--- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go
+++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go
@@ -13,7 +13,7 @@ import (
type ConnectionSecureApplyConfiguration struct {
PrivateKey *commonv0alpha1.InlineSecureValue `json:"privateKey,omitempty"`
ClientSecret *commonv0alpha1.InlineSecureValue `json:"clientSecret,omitempty"`
- Token *commonv0alpha1.InlineSecureValue `json:"webhook,omitempty"`
+ Token *commonv0alpha1.InlineSecureValue `json:"token,omitempty"`
}
// ConnectionSecureApplyConfiguration constructs a declarative configuration of the ConnectionSecure type for use with
diff --git a/conf/defaults.ini b/conf/defaults.ini
index 8e0a113a0ec..080d4e62fe0 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -336,7 +336,7 @@ rudderstack_data_plane_url =
rudderstack_sdk_url =
# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1
-rudderstack_v3_sdk_url =
+rudderstack_v3_sdk_url =
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
rudderstack_config_url =
@@ -2079,8 +2079,14 @@ enable =
# To enable features by default, set `Expression: "true"` in:
# https://github.com/grafana/grafana/blob/main/pkg/services/featuremgmt/registry.go
+# The feature_toggles section supports feature flags of a number of types,
+# including boolean, string, integer, float, and structured values, following the OpenFeature specification.
+#
# feature1 = true
# feature2 = false
+# feature3 = "foobar"
+# feature4 = 1.5
+# feature5 = { "foo": "bar" }
[feature_toggles.openfeature]
# This is EXPERIMENTAL. Please, do not use this section
diff --git a/conf/sample.ini b/conf/sample.ini
index 5a579d0e74e..b4bc6027abf 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -323,7 +323,7 @@
;rudderstack_sdk_url =
# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1
-;rudderstack_v3_sdk_url =
+;rudderstack_v3_sdk_url =
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
;rudderstack_config_url =
@@ -1913,7 +1913,7 @@ default_datasource_uid =
# client_queue_max_size is the maximum size in bytes of the client queue
# for Live connections. Defaults to 4MB.
-;client_queue_max_size =
+;client_queue_max_size =
#################################### Grafana Image Renderer Plugin ##########################
[plugin.grafana-image-renderer]
@@ -1996,9 +1996,14 @@ default_datasource_uid =
;enable = feature1,feature2
+# The feature_toggles section supports feature flags of a number of types,
+# including boolean, string, integer, float, and structured values, following the OpenFeature specification.
+
;feature1 = true
;feature2 = false
-
+;feature3 = "foobar"
+;feature4 = 1.5
+;feature5 = { "foo": "bar" }
[date_formats]
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
diff --git a/devenv/dev-dashboards/datasource-loki/loki_query_splitting.json b/devenv/dev-dashboards/datasource-loki/loki_query_splitting.json
index 0cc5135a0f4..44577d49ac4 100644
--- a/devenv/dev-dashboards/datasource-loki/loki_query_splitting.json
+++ b/devenv/dev-dashboards/datasource-loki/loki_query_splitting.json
@@ -216,8 +216,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -311,8 +310,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -493,8 +491,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -584,8 +581,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -675,8 +671,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -789,8 +784,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -903,8 +897,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -1018,8 +1011,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/devenv/dev-dashboards/panel-gauge/gauge_tests.json b/devenv/dev-dashboards/panel-gauge/gauge_tests.json
index f32ace420b4..f76f5d8809a 100644
--- a/devenv/dev-dashboards/panel-gauge/gauge_tests.json
+++ b/devenv/dev-dashboards/panel-gauge/gauge_tests.json
@@ -51,17 +51,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -122,17 +119,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -193,17 +187,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -255,7 +246,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -264,17 +254,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -326,7 +313,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -335,17 +321,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -397,7 +380,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -406,17 +388,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -468,7 +447,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [],
"max": 100,
"min": 0,
@@ -477,17 +455,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -641,7 +616,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"from": "",
@@ -660,17 +634,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -723,7 +694,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"from": "",
@@ -742,17 +712,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -805,7 +772,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"from": "0",
@@ -824,17 +790,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -887,7 +850,6 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "",
"mappings": [
{
"from": "0",
@@ -915,17 +877,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -991,7 +950,7 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "2",
+ "decimals": 2,
"mappings": [],
"max": 100,
"min": 0,
@@ -1000,17 +959,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -1071,7 +1027,7 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "2",
+ "decimals": 2,
"mappings": [],
"max": 100,
"min": 0,
@@ -1080,17 +1036,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -1152,7 +1105,7 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "2",
+ "decimals": 2,
"mappings": [],
"max": 100,
"min": 0,
@@ -1161,17 +1114,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
@@ -1233,7 +1183,7 @@
"mode": "thresholds"
},
"custom": {},
- "decimals": "2",
+ "decimals": 2,
"mappings": [],
"max": 100,
"min": 0,
@@ -1242,17 +1192,14 @@
"steps": [
{
"color": "#7EB26D",
- "index": 0,
"value": null
},
{
"color": "#ef843c",
- "index": 1,
"value": 75
},
{
"color": "#e24d42",
- "index": 2,
"value": 90
}
]
diff --git a/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json b/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json
index ff69226fdf5..c44353b7df2 100644
--- a/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json
+++ b/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json
@@ -2067,7 +2067,7 @@
}
],
"title": "Numeric, no series",
- "type": "gauge"
+ "type": "radialbar"
},
{
"datasource": {
@@ -2131,7 +2131,7 @@
}
],
"title": "Non-numeric",
- "type": "gauge"
+ "type": "radialbar"
}
],
"preload": false,
diff --git a/devenv/dev-dashboards/panel-histogram/histogram_tests.json b/devenv/dev-dashboards/panel-histogram/histogram_tests.json
index af6127cb447..7d6a684417e 100644
--- a/devenv/dev-dashboards/panel-histogram/histogram_tests.json
+++ b/devenv/dev-dashboards/panel-histogram/histogram_tests.json
@@ -58,8 +58,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -125,8 +124,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -192,8 +190,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -269,8 +266,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -343,8 +339,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -432,8 +427,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -516,8 +510,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -597,8 +590,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -678,8 +670,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
@@ -759,8 +750,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -824,8 +814,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": [
{
diff --git a/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json b/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json
index ac11fd803b9..4333993ea8e 100644
--- a/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json
+++ b/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json
@@ -248,7 +248,7 @@
"legend": {
"values": ["percent"],
"displayMode": "table",
- "placement": "right"
+ "placement": "bottom"
},
"pieType": "pie",
"reduceOptions": {
@@ -256,7 +256,7 @@
"fields": "",
"values": false
},
- "showLegend": true,
+ "showLegend": false,
"strokeWidth": 1,
"text": {}
},
@@ -272,15 +272,6 @@
"timeFrom": null,
"timeShift": null,
"title": "Percent",
- "transformations": [
- {
- "id": "renameByRegex",
- "options": {
- "regex": "^Backend-(.*)$",
- "renamePattern": "b-$1"
- }
- }
- ],
"type": "piechart"
},
{
@@ -320,7 +311,7 @@
"legend": {
"values": ["value"],
"displayMode": "table",
- "placement": "right"
+ "placement": "bottom"
},
"pieType": "pie",
"reduceOptions": {
@@ -328,7 +319,7 @@
"fields": "",
"values": false
},
- "showLegend": true,
+ "showLegend": false,
"strokeWidth": 1,
"text": {}
},
@@ -344,15 +335,6 @@
"timeFrom": null,
"timeShift": null,
"title": "Value",
- "transformations": [
- {
- "id": "renameByRegex",
- "options": {
- "regex": "(.*)",
- "renamePattern": "$1-how-much-wood-could-a-woodchuck-chuck-if-a-woodchuck-could-chuck-wood"
- }
- }
- ],
"type": "piechart"
},
{
diff --git a/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json b/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json
index b0ae2a9d76b..15fda5d6316 100644
--- a/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json
+++ b/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json
@@ -62,8 +62,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -150,8 +149,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -238,8 +236,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -326,8 +323,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -421,8 +417,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -516,8 +511,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
@@ -611,8 +605,7 @@
"value": 80
}
]
- },
- "unitScale": true
+ }
},
"overrides": []
},
diff --git a/docs/sources/administration/roles-and-permissions/_index.md b/docs/sources/administration/roles-and-permissions/_index.md
index c8135836fa1..7a33d940a15 100644
--- a/docs/sources/administration/roles-and-permissions/_index.md
+++ b/docs/sources/administration/roles-and-permissions/_index.md
@@ -35,10 +35,10 @@ For Grafana Cloud users, Grafana Support is not authorised to make org role chan
## Grafana server administrators
-A Grafana server administrator manages server-wide settings and access to resources such as organizations, users, and licenses. Grafana includes a default server administrator that you can use to manage all of Grafana, or you can divide that responsibility among other server administrators that you create.
+A Grafana server administrator (sometimes referred to as a **Grafana Admin**) manages server-wide settings and access to resources such as organizations, users, and licenses. Grafana includes a default server administrator that you can use to manage all of Grafana, or you can divide that responsibility among other server administrators that you create.
-{{< admonition type="note" >}}
-The server administrator role does not mean that the user is also a Grafana [organization administrator](#organization-roles).
+{{< admonition type="caution" >}}
+The server administrator role is distinct from the [organization administrator](#organization-roles) role.
{{< /admonition >}}
A server administrator can perform the following tasks:
@@ -50,7 +50,7 @@ A server administrator can perform the following tasks:
- Upgrade the server to Grafana Enterprise.
{{< admonition type="note" >}}
-The server administrator role does not exist in Grafana Cloud.
+The server administrator (Grafana Admin) role does not exist in Grafana Cloud.
{{< /admonition >}}
To assign or remove server administrator privileges, see [Server user management](../user-management/server-user-management/assign-remove-server-admin-privileges/).
diff --git a/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md b/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md
index 40a6d3645af..b0f35087efc 100644
--- a/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md
+++ b/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md
@@ -53,6 +53,11 @@ refs:
destination: /docs/grafana//administration/roles-and-permissions/access-control/custom-role-actions-scopes/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/account-management/authentication-and-permissions/access-control/custom-role-actions-scopes/
+ rbac-terraform-provisioning:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//administration/roles-and-permissions/access-control/rbac-terraform-provisioning/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana-cloud/account-management/authentication-and-permissions/access-control/rbac-terraform-provisioning/
rbac-grafana-provisioning:
- pattern: /docs/grafana/
destination: /docs/grafana//administration/roles-and-permissions/access-control/rbac-grafana-provisioning/
@@ -145,7 +150,13 @@ Refer to the [RBAC HTTP API](ref:api-rbac-get-a-role) for more details.
## Create custom roles
-This section shows you how to create a custom RBAC role using Grafana provisioning and the HTTP API.
+This section shows you how to create a custom RBAC role using Grafana provisioning or the HTTP API.
+
+Creating and editing custom roles is not currently possible in the Grafana UI. To manage custom roles, use one of the following methods:
+
+- [Provisioning](ref:rbac-grafana-provisioning) (for self-managed instances)
+- [HTTP API](ref:api-rbac-create-a-new-custom-role)
+- [Terraform](ref:rbac-terraform-provisioning)
Create a custom role when basic roles and fixed roles do not meet your permissions requirements.
@@ -153,14 +164,101 @@ Create a custom role when basic roles and fixed roles do not meet your permissio
- [Plan your RBAC rollout strategy](ref:plan-rbac-rollout-strategy).
- Determine which permissions you want to add to the custom role. To see a list of actions and scope, refer to [RBAC permissions, actions, and scopes](ref:custom-role-actions-scopes).
-- [Enable role provisioning](ref:rbac-grafana-provisioning).
- Ensure that you have permissions to create a custom role.
- By default, the Grafana Admin role has permission to create custom roles.
- A Grafana Admin can delegate the custom role privilege to another user by creating a custom role with the relevant permissions and adding the `permissions:type:delegate` scope.
-### Create custom roles using provisioning
+### Create custom roles using the HTTP API
-[File-based provisioning](ref:rbac-grafana-provisioning) is one method you can use to create custom roles.
+The following examples show you how to create a custom role using the Grafana HTTP API. For more information about the HTTP API, refer to [Create a new custom role](ref:api-rbac-create-a-new-custom-role).
+
+{{< admonition type="note" >}}
+When you create a custom role you can only give it the same permissions you already have. For example, if you only have `users:create` permissions, then you can't create a role that includes other permissions.
+{{< /admonition >}}
+
+The following example creates a `custom:users:admin` role and assigns the `users:create` action to it.
+
+**Example request**
+
+```
+curl --location --request POST '/api/access-control/roles/' \
+--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "version": 1,
+ "uid": "jZrmlLCkGksdka",
+ "name": "custom:users:admin",
+ "displayName": "custom users admin",
+ "description": "My custom role which gives users permissions to create users",
+ "global": true,
+ "permissions": [
+ {
+ "action": "users:create"
+ }
+ ]
+}'
+```
+
+**Example response**
+
+```
+{
+ "version": 1,
+ "uid": "jZrmlLCkGksdka",
+ "name": "custom:users:admin",
+ "displayName": "custom users admin",
+ "description": "My custom role which gives users permissions to create users",
+ "global": true,
+ "permissions": [
+ {
+ "action": "users:create"
+ "updated": "2021-05-17T22:07:31.569936+02:00",
+ "created": "2021-05-17T22:07:31.569935+02:00"
+ }
+ ],
+ "updated": "2021-05-17T22:07:31.564403+02:00",
+ "created": "2021-05-17T22:07:31.564403+02:00"
+}
+```
+
+Refer to the [RBAC HTTP API](ref:api-rbac-create-a-new-custom-role) for more details.
+
+### Create custom roles using Terraform
+
+You can use the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs) to manage custom roles and their assignments. This is the recommended method for Grafana Cloud users who want to manage RBAC as code. For more information, refer to [Provisioning RBAC with Terraform](ref:rbac-terraform-provisioning).
+
+The following example creates a custom role and assigns it to a team:
+
+```terraform
+resource "grafana_role" "custom_folder_manager" {
+ name = "custom:folders:manager"
+ description = "Custom role for reading and creating folders"
+ uid = "custom-folders-manager"
+ version = 1
+ global = true
+
+ permissions {
+ action = "folders:read"
+ scope = "folders:*"
+ }
+
+ permissions {
+ action = "folders:create"
+ scope = "folders:uid:general" # Allows creating folders at the root level
+ }
+}
+
+resource "grafana_role_assignment" "custom_folder_manager_assignment" {
+ role_uid = grafana_role.custom_folder_manager.uid
+ teams = [""]
+}
+```
+
+For more information, refer to the [`grafana_role`](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/role) and [`grafana_role_assignment`](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/role_assignment) documentation in the Terraform Registry.
+
+### Create custom roles using file-based provisioning
+
+You can use [file-based provisioning](ref:rbac-grafana-provisioning) to create custom roles for self-managed instances.
1. Open the YAML configuration file and locate the `roles` section.
@@ -251,61 +349,6 @@ roles:
state: 'absent'
```
-### Create custom roles using the HTTP API
-
-The following examples show you how to create a custom role using the Grafana HTTP API. For more information about the HTTP API, refer to [Create a new custom role](ref:api-rbac-create-a-new-custom-role).
-
-{{< admonition type="note" >}}
-You cannot create a custom role with permissions that you do not have. For example, if you only have `users:create` permissions, then you cannot create a role that includes other permissions.
-{{< /admonition >}}
-
-The following example creates a `custom:users:admin` role and assigns the `users:create` action to it.
-
-**Example request**
-
-```
-curl --location --request POST '/api/access-control/roles/' \
---header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
---header 'Content-Type: application/json' \
---data-raw '{
- "version": 1,
- "uid": "jZrmlLCkGksdka",
- "name": "custom:users:admin",
- "displayName": "custom users admin",
- "description": "My custom role which gives users permissions to create users",
- "global": true,
- "permissions": [
- {
- "action": "users:create"
- }
- ]
-}'
-```
-
-**Example response**
-
-```
-{
- "version": 1,
- "uid": "jZrmlLCkGksdka",
- "name": "custom:users:admin",
- "displayName": "custom users admin",
- "description": "My custom role which gives users permissions to create users",
- "global": true,
- "permissions": [
- {
- "action": "users:create"
- "updated": "2021-05-17T22:07:31.569936+02:00",
- "created": "2021-05-17T22:07:31.569935+02:00"
- }
- ],
- "updated": "2021-05-17T22:07:31.564403+02:00",
- "created": "2021-05-17T22:07:31.564403+02:00"
-}
-```
-
-Refer to the [RBAC HTTP API](ref:api-rbac-create-a-new-custom-role) for more details.
-
## Update basic role permissions
If the default basic role definitions do not meet your requirements, you can change their permissions.
diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md
index 15cf895a5e8..465880919ca 100644
--- a/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md
+++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-for-app-plugins/index.md
@@ -66,17 +66,18 @@ Please refer to plugin documentation to see what RBAC permissions the plugin has
The following list contains app plugins that have fine-grained RBAC support.
-| App plugin | App plugin ID | App plugin permission documentation |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) |
-| [Adaptive Metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) |
-| [Cloud Provider](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/) | `grafana-csp-app` | [Cloud Provider Observability role-based access control](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/rbac/) |
-| [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a |
-| [Kubernetes Monitoring](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/) | `grafana-k8s-app` | [Kubernetes Monitoring role-based access control](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/control-access/#precision-access-with-rbac-custom-plugin-roles) |
-| [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) |
-| [Performance Testing (K6)](https://grafana.com/docs/grafana-cloud/testing/k6/) | `k6-app` | [Configure RBAC for K6](https://grafana.com/docs/grafana-cloud/testing/k6/projects-and-users/configure-rbac/) |
-| [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) | `grafana-pdc-app` | n/a |
-| [Service Level Objective (SLO)](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/) | `grafana-slo-app` | [Configure RBAC for SLO](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/rbac/) |
+| App plugin | App plugin ID | App plugin permission documentation |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) |
+| [Adaptive Metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) |
+| [Cloud Provider](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/) | `grafana-csp-app` | [Cloud Provider Observability role-based access control](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/rbac/) |
+| [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a |
+| [Kubernetes Monitoring](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/) | `grafana-k8s-app` | [Kubernetes Monitoring role-based access control](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/control-access/#precision-access-with-rbac-custom-plugin-roles) |
+| [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) |
+| [Performance Testing (K6)](https://grafana.com/docs/grafana-cloud/testing/k6/) | `k6-app` | [Configure RBAC for K6](https://grafana.com/docs/grafana-cloud/testing/k6/projects-and-users/configure-rbac/) |
+| [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) | `grafana-pdc-app` | n/a |
+| [Service Level Objective (SLO)](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/) | `grafana-slo-app` | [Configure RBAC for SLO](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/rbac/) |
+| [Synthetic Monitoring](https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/) | `grafana-synthetic-monitoring-app` | [Configure RBAC for Synthetic Monitoring](https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/user-and-team-management/) |
### Revoke fine-grained access from app plugins
diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md
index 06f9699533b..fe45a620bc5 100644
--- a/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md
+++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md
@@ -6,7 +6,6 @@ description: Learn about RBAC Grafana provisioning and view an example YAML prov
file that configures Grafana role assignments.
labels:
products:
- - cloud
- enterprise
menuTitle: Provisioning RBAC with Grafana
title: Provisioning RBAC with Grafana
@@ -52,11 +51,13 @@ refs:
# Provisioning RBAC with Grafana
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud).
+Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) for self-managed instances. This feature is not available in Grafana Cloud.
{{< /admonition >}}
You can create, change or remove [Custom roles](ref:manage-rbac-roles-create-custom-roles-using-provisioning) and create or remove [basic role assignments](ref:assign-rbac-roles-assign-a-fixed-role-to-a-basic-role-using-provisioning), by adding one or more YAML configuration files in the `provisioning/access-control/` directory.
+Because this method requires access to the file system where Grafana is running, it's only available for self-managed Grafana instances. To provision RBAC in Grafana Cloud, use [Terraform](ref:rbac-terraform-provisioning) or the [HTTP API](ref:api-rbac-create-and-manage-custom-roles).
+
Grafana performs provisioning during startup. After you make a change to the configuration file, you can reload it during runtime. You do not need to restart the Grafana server for your changes to take effect.
**Before you begin:**
diff --git a/docs/sources/as-code/observability-as-code/_index.md b/docs/sources/as-code/observability-as-code/_index.md
index 338d9235255..92441bd5110 100644
--- a/docs/sources/as-code/observability-as-code/_index.md
+++ b/docs/sources/as-code/observability-as-code/_index.md
@@ -25,10 +25,6 @@ cards:
height: 24
href: ./foundation-sdk/
description: The Grafana Foundation SDK is a set of tools, types, and libraries that let you define Grafana dashboards and resources using familiar programming languages like Go, TypeScript, Python, Java, and PHP. Use it in conjunction with `grafanactl` to push your programmatically generated resources.
- - title: JSON schema v2
- height: 24
- href: ./schema-v2/
- description: Grafana dashboards are represented as JSON objects that store metadata, panels, variables, and settings. Observability as Code works with all versions of the JSON model, and it's fully compatible with version 2.
- title: Git Sync (private preview)
height: 24
href: ./provision-resources/intro-git-sync/
@@ -68,7 +64,7 @@ Historically, managing Grafana as code involved various community and Grafana La
- This approach requires handling HTTP requests and responses but provides complete control over resource management.
- `grafanactl`, Git Sync, and the Foundation SDK are all built on top of these APIs.
-- To understand Dashboard Schemas accepted by the APIs, refer to the [JSON models documentation](https://grafana.com/docs/grafana//observability-as-code/schema-v2/).
+- To understand Dashboard Schemas accepted by the APIs, refer to the [JSON models documentation](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md).
## Explore
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/_index.md b/docs/sources/as-code/observability-as-code/schema-v2/_index.md
deleted file mode 100644
index 65c73a49cbe..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/_index.md
+++ /dev/null
@@ -1,243 +0,0 @@
----
-description: A reference for the JSON dashboard schemas used with Observability as Code, including the experimental V2 schema.
-keywords:
- - configuration
- - as code
- - dashboards
- - git integration
- - git sync
- - github
-labels:
- products:
- - cloud
- - enterprise
- - oss
-title: JSON schema v2
-weight: 500
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/
-aliases:
- - ../../observability-as-code/schema-v2/ # /docs/grafana/next/observability-as-code/schema-v2/
----
-
-# Dashboard JSON schema v2
-
-{{< admonition type="caution" >}}
-
-Dashboard JSON schema v2 is an [experimental](https://grafana.com/docs/release-life-cycle/) feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog).
-
-**Do not enable this feature in production environments as it may result in the irreversible loss of data.**
-
-{{< /admonition >}}
-
-Grafana dashboards are represented as JSON objects that store metadata, panels, variables, and settings.
-
-Observability as Code works with all versions of the JSON model, and it's fully compatible with version 2.
-
-## Before you begin
-
-Schema v2 is automatically enabled with the Dynamic Dashboards feature toggle.
-To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog).
-It also requires the new dashboards API feature toggle, `kubernetesDashboards`, to be enabled as well.
-
-For more information on how dashboards behave depending on your feature flag configuration, refer to [Notes and limitations](#notes-and-limitations).
-
-## Accessing the JSON Model
-
-To view the JSON representation of a dashboard:
-
-1. Toggle on the edit mode switch in the top-right corner of the dashboard.
-1. Click the gear icon in the top navigation bar to go to **Settings**.
-1. Select the **JSON Model** tab.
-1. Copy or edit the JSON structure as needed.
-
-## JSON fields
-
-```json
-{
- "annotations": [],
- "cursorSync": "Off",
- "editable": true,
- "elements": {},
- "layout": {
- "kind": GridLayout, // Can also be AutoGridLayout, RowsLayout, or TabsLayout
- "spec": {
- "items": []
- }
- },
- "links": [],
- "liveNow": false,
- "preload": false,
- "tags": [], // Tags associated with the dashboard.
- "timeSettings": {
- "autoRefresh": "",
- "autoRefreshIntervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ],
- "fiscalYearStartMonth": 0,
- "from": "now-6h",
- "hideTimepicker": false,
- "timezone": "browser",
- "to": "now"
- },
- "title": "",
- "variables": []
-},
-```
-
-The dashboard JSON sample shown uses the default `GridLayoutKind`.
-The JSON in a new dashboard for the other three layout options, `AutoGridLayout`, `RowsLayout`, and `TabsLayout`, are as follows:
-
-**`AutoGridLayout`**
-
-```json
- "layout": {
- "kind": "AutoGridLayout",
- "spec": {
- "columnWidthMode": "standard",
- "items": [],
- "fillScreen": false,
- "maxColumnCount": 3,
- "rowHeightMode": "standard"
- }
- },
-```
-
-**`RowsLayout`**
-
-```json
- "layout": {
- "kind": "RowsLayout",
- "spec": {
- "rows": []
- },
-```
-
-**`TabsLayout`**
-
-```json
- "layout": {
- "kind": "TabsLayout",
- "spec": {
- "tabs": []
- },
-```
-
-### `DashboardSpec`
-
-The following table explains the usage of the dashboard JSON fields.
-The table includes default and other fields:
-
-
-
-| Name | Usage |
-| ------------ | ------------------------------------------------------------------------- |
-| annotations | Contains the list of annotations that are associated with the dashboard. |
-| cursorSync | Dashboard cursor sync behavior.- `Off` - No shared crosshair or tooltip (default)
- `Crosshair` - Shared crosshair
- `Tooltip` - Shared crosshair and shared tooltip
|
-| editable | bool. Whether or not a dashboard is editable. |
-| elements | Contains the list of elements included in the dashboard. Supported dashboard elements are: PanelKind and LibraryPanelKind. |
-| layout | The dashboard layout. Supported layouts are:- GridLayoutKind
- AutoGridLayoutKind
- RowsLayoutKind
- TabsLayoutKind
|
-| links | Links with references to other dashboards or external websites. |
-| liveNow | bool. When set to `true`, the dashboard redraws panels at an interval matching the pixel width. This keeps data "moving left" regardless of the query refresh rate. This setting helps avoid dashboards presenting stale live data. |
-| preload | bool. When set to `true`, the dashboard loads all panels when the dashboard is loaded. |
-| tags | Contains the list of tags associated with dashboard. |
-| timeSettings | All time settings for the dashboard. |
-| title | Title of the dashboard. |
-| variables | Contains the list of configured template variables. |
-
-
-
-### `annotations`
-
-The configuration for the list of annotations that are associated with the dashboard.
-For the JSON and field usage notes, refer to the [annotations schema documentation](https://grafana.com/docs/grafana//observability-as-code/schema-v2/annotations-schema/).
-
-### `elements`
-
-Dashboards can contain the following elements:
-
-- [PanelKind](https://grafana.com/docs/grafana//observability-as-code/schema-v2/panel-schema/)
-- [LibraryPanelKind](https://grafana.com/docs/grafana//observability-as-code/schema-v2/librarypanel-schema/)
-
-### `layout`
-
-Dashboards can have four layout options:
-
-- [GridLayoutKind](https://grafana.com/docs/grafana//observability-as-code/schema-v2/layout-schema/#gridlayoutkind)
-- [AutoGridLayoutKind](https://grafana.com/docs/grafana//observability-as-code/schema-v2/layout-schema/#autogridlayoutkind)
-- [RowsLayoutKind](https://grafana.com/docs/grafana//observability-as-code/schema-v2/layout-schema/#rowslayoutkind)
-- [TabsLayoutKind](https://grafana.com/docs/grafana//observability-as-code/schema-v2/layout-schema/#tabslayoutkind)
-
-For the JSON and field usage notes about each of these, refer to the [layout schema documentation](https://grafana.com/docs/grafana//observability-as-code/schema-v2/layout-schema/).
-
-### `links`
-
-The configuration for links with references to other dashboards or external websites.
-
-For the JSON and field usage notes, refer to the [links schema documentation](https://grafana.com/docs/grafana//observability-as-code/schema-v2/links-schema/).
-
-### `tags`
-
-Tags associated with the dashboard. Each tag can be up to 50 characters long.
-
-` [...string]`
-
-### `timesettings`
-
-The `TimeSettingsSpec` defines the default time configuration for the time picker and the refresh picker for the specific dashboard.
-For the JSON and field usage notes about the `TimeSettingsSpec`, refer to the [timesettings schema documentation](https://grafana.com/docs/grafana//observability-as-code/schema-v2/timesettings-schema/).
-
-### `variables`
-
-The `variables` schema defines which variables are used in the dashboard.
-
-There are eight variables types:
-
-- QueryVariableKind
-- TextVariableKind
-- ConstantVariableKind
-- DatasourceVariableKind
-- IntervalVariableKind
-- CustomVariableKind
-- GroupByVariableKind
-- AdhocVariableKind
-
-For the JSON and field usage notes about the `variables` spec, refer to the [variables schema documentation](https://grafana.com/docs/grafana//observability-as-code/schema-v2/variables-schema/).
-
-## Notes and limitations
-
-### Existing dashboards
-
-With schema v2 enabled, you can still open and view your pre-existing dashboards.
-Upon saving, they’ll be updated to the new schema where you can take advantage of the new features and functionalities.
-
-### Dashboard behavior with disabled feature flags
-
-If you disable the Dynamic dashboards or `kubernetesDashboards` feature flags, you should be aware of how dashboards will behave.
-
-#### Disable Dynamic dashboards
-
-If the Dynamic dashboards feature toggle is disabled, depending on how the dashboard was built, it will behave differently:
-
-- Dashboards built on the new schema through the UI - View only
-- Dashboards built on Schema v1 - View and edit
-- Dashboards built on the new schema by way of Terraform or the CLI - View and edit
-- Provisioned dashboards built on the new schema - View and edit, but the edit experience will be the old experience
-
-#### Disable Dynamic dashboards and `kubernetesDashboards`
-
-You’ll be unable to view or edit dashboards created or updated in the new schema.
-
-### Import and export
-
-From the UI, dashboards created on schema v2 can be exported and imported like other dashboards.
-When you export them to use in another instance, references of data sources are not persisted but data source types are.
-You’ll have the option to select the data source of your choice in the import UI.
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/annotations-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/annotations-schema.md
deleted file mode 100644
index e99e7c2cce6..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/annotations-schema.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-description: A reference for the JSON annotations schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - annotations
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: annotations schema
-title: annotations
-weight: 100
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/annotations-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/annotations-schema/ # /docs/grafana/next/observability-as-code/schema-v2/annotations-schema/
----
-
-# `annotations`
-
-The configuration for the list of annotations that are associated with the dashboard.
-
-```json
- "annotations": [
- {
- "kind": "AnnotationQuery",
- "spec": {
- "builtIn": false,
- "datasource": {
- "type": "",
- "uid": ""
- },
- "enable": false,
- "hide": false,
- "iconColor": "",
- "name": ""
- }
- }
- ],
-```
-
-`AnnotationsQueryKind` consists of:
-
-- kind: "AnnotationQuery"
-- spec: [AnnotationQuerySpec](#annotationqueryspec)
-
-## `AnnotationQuerySpec`
-
-| Name | Type/Definition |
-| ---------- | ----------------------------------------------------------------- |
-| datasource | [`DataSourceRef`](#datasourceref) |
-| query | [`DataQueryKind`](#dataquerykind) |
-| enable | bool |
-| hide | bool |
-| iconColor | string |
-| name | string |
-| builtIn | bool. Default is `false`. |
-| filter | [`AnnotationPanelFilter`](#annotationpanelfilter) |
-| options | `[string]`: A catch-all field for datasource-specific properties. |
-
-### `DataSourceRef`
-
-| Name | Usage |
-| ----- | ---------------------------------- |
-| type? | string. The plugin type-id. |
-| uid? | The specific data source instance. |
-
-### `DataQueryKind`
-
-| Name | Type |
-| ---- | ------ |
-| kind | string |
-| spec | string |
-
-### `AnnotationPanelFilter`
-
-| Name | Type/Definition |
-| -------- | ------------------------------------------------------------------------------ |
-| exclude? | bool. Should the specified panels be included or excluded. Default is `false`. |
-| ids | `[...uint8]`. Panel IDs that should be included or excluded. |
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/layout-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/layout-schema.md
deleted file mode 100644
index ca31417bbcf..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/layout-schema.md
+++ /dev/null
@@ -1,339 +0,0 @@
----
-description: A reference for the JSON layout schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - layout
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: layout schema
-title: layout
-weight: 400
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/layout-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/layout-schema/ # /docs/grafana/next/observability-as-code/schema-v2/layout-schema/
----
-
-# `layout`
-
-There are four layout options offering two types of panel control:
-
-**Panel layout options**
-
-These options control the size and position of panels:
-
-- [GridLayoutKind](#gridlayoutkind) - Corresponds to the **Custom** option in the UI. You define panel size and panel positions using x- and y- settings.
-- [AutoGridLayoutKind](#autogridlayoutkind) - Corresponds to the **Auto grid** option in the UI. Panel size and position are automatically set based on column and row parameters.
-
-**Panel grouping options**
-
-These options control the grouping of panels:
-
-- [RowsLayoutKind](#rowslayoutkind) - Groups panels into rows.
-- [TabsLayoutKind](#tabslayoutkind) - Groups panels into tabs.
-
-## `GridLayoutKind`
-
-The grid layout allows you to manually size and position grid items by setting the height, width, x, and y of each item.
-This layout corresponds to the **Custom** option in the UI.
-
-Following is the JSON for a default grid layout, a grid layout item, and a grid layout row:
-
-```json
- "kind": "GridLayout",
- "spec": {
- "items": [
- {
- "kind": "GridLayoutItem",
- "spec": {
- "element": {...},
- "height": 0,
- "width": 0,
- "x": 0,
- "y": 0
- }
- },
- {
- "kind": "GridLayoutRow",
- "spec": {
- "collapsed": false,
- "elements": [],
- "title": "",
- "y": 0
- }
- },
- ]
- }
-```
-
-`GridLayoutKind` consists of:
-
-- kind: "GridLayout"
-- spec: GridLayoutSpec
- - items: GridLayoutItemKind` or GridLayoutRowKind`
- - GridLayoutItemKind
- - kind: "GridLayoutItem"
- - spec: [GridLayoutItemSpec](#gridlayoutitemspec)
- - GridLayoutRowKind
- - kind: "GridLayoutRow"
- - spec: [GridLayoutRowSpec](#gridlayoutrowspec)
-
-### `GridLayoutItemSpec`
-
-The following table explains the usage of the grid layout item JSON fields:
-
-| Name | Usage |
-| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| x | integer. Position of the item x-axis. |
-| y | integer. Position of the item y-axis. |
-| width | Width of the item in pixels. |
-| height | Height of the item in pixels. |
-| element | `ElementReference`. Reference to a [`PanelKind`](https://grafana.com/docs/grafana//observability-as-code/schema-v2/panel-schema/) from `dashboard.spec.elements` expressed as JSON Schema reference. |
-| repeat? | [RepeatOptions](#repeatoptions). Configured repeat options, if any |
-
-#### `RepeatOptions`
-
-The following table explains the usage of the repeat option JSON fields:
-
-| Name | Usage |
-| ---------- | ---------------------------------------------------- |
-| mode | `RepeatMode` - "variable" |
-| value | string |
-| direction? | Options are `h` for horizontal and `v` for vertical. |
-| maxPerRow? | integer |
-
-### `GridLayoutRowSpec`
-
-The following table explains the usage of the grid layout row JSON fields:
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| y | integer. Position of the row y-axis |
-| collapsed | bool. Whether or not the row is collapsed |
-| title | Row title |
-| elements | [`[...GridLayoutItemKind]`](#gridlayoutitemspec). Grid items in the row will have their y value be relative to the row's y value. This means a panel positioned at `y: 0` in a row with `y: 10` will be positioned at `y: 11` (row header has a height of 1) in the dashboard. |
-| repeat? | [RowRepeatOptions](#rowrepeatoptions) Configured row repeat options, if any
|
-
-
-
-#### `RowRepeatOptions`
-
-| Name | Usage |
-| ----- | ------------------------- |
-| mode | `RepeatMode` - "variable" |
-| value | string |
-
-## `AutoGridLayoutKind`
-
-With an auto grid, Grafana sizes and positions your panels for the best fit based on the column and row constraints that you set.
-This layout corresponds to the **Auto grid** option in the UI.
-
-Following is the JSON for a default auto grid layout and a grid layout item:
-
-
-
-```json
- "kind": "AutoGridLayout",
- "spec": {
- "columnWidthMode": "standard",
- "fillScreen": false,
- "items": [
- {
- "kind": "AutoGridLayoutItem",
- "spec": {
- "element": {...},
- }
- }
- ],
- "maxColumnCount": 3,
- "rowHeightMode": "standard"
- }
-```
-
-`AutoGridLayoutKind` consists of:
-
-- kind: "AutoGridLayout"
-- spec: [AutoGridLayoutSpec](#autogridlayoutspec)
-
-### `AutoGridLayoutSpec`
-
-The following table explains the usage of the auto grid layout JSON fields:
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| maxColumnCount? | number. Default is `3`. |
-| columnWidthMode | Options are: `narrow`, `standard`, `wide`, and `custom`. Default is `standard`. |
-| columnWidth? | number |
-| rowHeightMode | Options are: `short`, `standard`, `tall`, and `custom`. Default is `standard`. |
-| rowHeight? | number |
-| fillScreen? | bool. Default is `false`. |
-| items | `AutoGridLayoutItemKind`. Consists of:- kind: "AutoGridLayoutItem"
- spec: [AutoGridLayoutItemSpec](#autogridlayoutitemspec)
|
-
-
-
-#### `AutoGridLayoutItemSpec`
-
-The following table explains the usage of the auto grid layout item JSON fields:
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| element | `ElementReference`. Reference to a [`PanelKind`](https://grafana.com/docs/grafana//observability-as-code/schema-v2/panel-schema/) from `dashboard.spec.elements` expressed as JSON Schema reference. |
-| repeat? | [AutoGridRepeatOptions](#autogridrepeatoptions). Configured repeat options, if any. |
-| conditionalRendering? | `ConditionalRenderingGroupKind`. Rules for hiding or showing panels, if any. Consists of:- kind: "ConditionalRenderingGroup"
- spec: [ConditionalRenderingGroupSpec](#conditionalrenderinggroupspec)
|
-
-
-
-##### `AutoGridRepeatOptions`
-
-The following table explains the usage of the auto grid repeat option JSON fields:
-
-| Name | Usage |
-| ----- | ------------------------- |
-| mode | `RepeatMode` - "variable" |
-| value | String |
-
-##### `ConditionalRenderingGroupSpec`
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| visibility | Options are `show` and `hide` |
-| condition | Options are `and` and `or` |
-| items | Options are:- ConditionalRenderingVariableKind
- kind: "ConditionalRenderingVariable"
- spec: [ConditionalRenderingVariableSpec](#conditionalrenderingvariablespec)
- ConditionalRenderingDataKind
- kind: "ConditionalRenderingData"
- spec: [ConditionalRenderingDataSpec](#conditionalrenderingdataspec)
- ConditionalRenderingTimeRangeSizeKind
- kind: "ConditionalRenderingTimeRangeSize"
- spec: [ConditionalRenderingTimeRangeSizeSpec](#conditionalrenderingtimerangesizespec)
|
-
-
-
-###### `ConditionalRenderingVariableSpec`
-
-| Name | Usage |
-| -------- | ------------------------------------ |
-| variable | string |
-| operator | Options are `equals` and `notEquals` |
-| value | string |
-
-###### `ConditionalRenderingDataSpec`
-
-| Name | Type |
-| ----- | ---- |
-| value | bool |
-
-###### `ConditionalRenderingTimeRangeSizeSpec`
-
-| Name | Type |
-| ----- | ------ |
-| value | string |
-
-## `RowsLayoutKind`
-
-The `RowsLayoutKind` is one of two options that you can use to group panels.
-You can nest any other kind of layout inside a layout row.
-Rows can also be nested in auto grids or tabs.
-
-Following is the JSON for a default rows layout row:
-
-```json
- "kind": "RowsLayout",
- "spec": {
- "rows": [
- {
- "kind": "RowsLayoutRow",
- "spec": {
- "layout": {
- "kind": "GridLayout", // Can also be AutoGridLayout or TabsLayout
- "spec": {...}
- },
- "title": ""
- }
- }
- ]
- }
-```
-
-`RowsLayoutKind` consists of:
-
-- kind: RowsLayout
-- spec: RowsLayoutSpec
- - rows: RowsLayoutRowKind
- - kind: RowsLayoutRow
- - spec: [RowsLayoutRowSpec](#rowslayoutrowspec)
-
-### `RowsLayoutRowSpec`
-
-The following table explains the usage of the rows layout row JSON fields:
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| title? | Title of the row. |
-| collapse | bool. Whether or not the row is collapsed. |
-| hideHeader? | bool. Whether the row header is hidden or shown. |
-| fullScreen? | bool. Whether or not the row takes up the full screen. |
-| conditionalRendering? | `ConditionalRenderingGroupKind`. Rules for hiding or showing rows, if any. Consists of:- kind: "ConditionalRenderingGroup"
- spec: [ConditionalRenderingGroupSpec](#conditionalrenderinggroupspec)
|
-| repeat? | [RowRepeatOptions](#rowrepeatoptions). Configured repeat options, if any. |
-| layout | Supported layouts are:- [GridLayoutKind](#gridlayoutkind)
- [RowsLayoutKind](#rowslayoutkind)
- [AutoGridLayoutKind](#autogridlayoutkind)
- [TabsLayoutKind](#tabslayoutkind)
|
-
-
-
-## `TabsLayoutKind`
-
-The `TabsLayoutKind` is one of two options that you can use to group panels.
-You can nest any other kind of layout inside a tab.
-Tabs can also be nested in auto grids or rows.
-
-Following is the JSON for a default tabs layout tab and a tab:
-
-```json
- "kind": "TabsLayout",
- "spec": {
- "tabs": [
- {
- "kind": "TabsLayoutTab",
- "spec": {
- "layout": {
- "kind": "GridLayout", // Can also be AutoGridLayout or RowsLayout
- "spec": {...}
- },
- "title": "New tab"
- }
- }
- ]
- }
-```
-
-`TabsLayoutKind` consists of:
-
-- kind: TabsLayout
- - spec: TabsLayoutSpec
- - tabs: TabsLayoutTabKind
- - kind: TabsLayoutTab
- - spec: [TabsLayoutTabSpec](#tabslayouttabspec)
-
-### `TabsLayoutTabSpec`
-
-The following table explains the usage of the tabs layout tab JSON fields:
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| title? | The title of the tab. |
-| layout | Supported layouts are:- [GridLayoutKind](#gridlayoutkind)
- [RowsLayoutKind](#rowslayoutkind)
- [AutoGridLayoutKind](#autogridlayoutkind)
- [TabsLayoutKind](#tabslayoutkind)
|
-| conditionalRendering? | `ConditionalRenderingGroupKind`. Rules for hiding or showing panels, if any. Consists of:- kind: "ConditionalRenderingGroup"
- spec: [ConditionalRenderingGroupSpec](#conditionalrenderinggroupspec)
|
-
-
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/librarypanel-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/librarypanel-schema.md
deleted file mode 100644
index 45715e15b15..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/librarypanel-schema.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-description: A reference for the JSON library panel schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - library panel
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: LibraryPanelKind schema
-title: LibraryPanelKind
-weight: 300
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/librarypanel-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/librarypanel-schema/ # /docs/grafana/next/observability-as-code/schema-v2/librarypanel-schema/
----
-
-# `LibraryPanelKind`
-
-A library panel is a reusable panel that you can use in any dashboard.
-When you make a change to a library panel, that change propagates to all instances of where the panel is used.
-Library panels streamline reuse of panels across multiple dashboards.
-
-Following is the default library panel element JSON:
-
-```json
- "kind": "LibraryPanel",
- "spec": {
- "id": 0,
- "libraryPanel": {
- name: "",
- uid: "",
- }
- "title": ""
- }
-```
-
-The `LibraryPanelKind` consists of:
-
-- kind: "LibraryPanel"
-- spec: [LibraryPanelKindSpec](#librarypanelkindspec)
- - libraryPanel: [LibraryPanelRef](#librarypanelref)
-
-## `LibraryPanelKindSpec`
-
-The following table explains the usage of the library panel element JSON fields:
-
-| Name | Usage |
-| ------------ | ------------------------------------------------ |
-| id | Panel ID for the library panel in the dashboard. |
-| libraryPanel | [`LibraryPanelRef`](#librarypanelref) |
-| title | Title for the library panel in the dashboard. |
-
-### `LibraryPanelRef`
-
-The following table explains the usage of the library panel reference JSON fields:
-
-| Name | Usage |
-| ---- | ------------------ |
-| name | Library panel name |
-| uid | Library panel uid |
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/links-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/links-schema.md
deleted file mode 100644
index 0ddc50376de..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/links-schema.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-description: A reference for the JSON links schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - links
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: links schema
-title: links
-weight: 500
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/links-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/links-schema/ # /docs/grafana/next/observability-as-code/schema-v2/links-schema/
----
-
-# `links`
-
-The `links` schema is the configuration for links with references to other dashboards or external websites.
-Following are the default JSON fields:
-
-```json
- "links": [
- {
- "asDropdown": false,
- "icon": "",
- "includeVars": false,
- "keepTime": false,
- "tags": [],
- "targetBlank": false,
- "title": "",
- "tooltip": "",
- "type": "link",
- },
- ],
-```
-
-## `DashboardLink`
-
-The following table explains the usage of the dashboard link JSON fields.
-The table includes default and other fields:
-
-
-
-| Name | Usage |
-| ----------- | --------------------------------------- |
-| title | string. Title to display with the link. |
-| type | `DashboardLinkType`. Link type. Accepted values are:- dashboards - To refer to another dashboard
- link - To refer to an external resource
|
-| icon | string. Icon name to be displayed with the link. |
-| tooltip | string. Tooltip to display when the user hovers their mouse over it. |
-| url? | string. Link URL. Only required/valid if the type is link. |
-| tags | string. List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards. |
-| asDropdown | bool. If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards. Default is `false`. |
-| targetBlank | bool. If true, the link will be opened in a new tab. Default is `false`. |
-| includeVars | bool. If true, includes current template variables values in the link as query params. Default is `false`. |
-| keepTime | bool. If true, includes current time range in the link as query params. Default is `false`. |
-| placement? | string. Use placement to display the link somewhere else on the dashboard other than above the visualizations. Use the `inControlsMenu` parameter to render the link in the dashboard controls dropdown menu. |
-
-
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/panel-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/panel-schema.md
deleted file mode 100644
index 088ab8eebf4..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/panel-schema.md
+++ /dev/null
@@ -1,305 +0,0 @@
----
-description: A reference for the JSON panel schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - panels
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: PanelKind schema
-title: PanelKind
-weight: 200
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/panel-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/panel-schema/ # /docs/grafana/next/observability-as-code/schema-v2/panel-schema/
----
-
-# `PanelKind`
-
-The panel element contains all the information about the panel including the visualization type, panel and visualization configuration, queries, and transformations.
-There's a panel element for each panel contained in the dashboard.
-
-Following is the default panel element JSON:
-
-```json
- "kind": "Panel",
- "spec": {
- "data": {
- "kind": "QueryGroup",
- "spec": {...},
- "description": "",
- "id": 0,
- "links": [],
- "title": "",
- "vizConfig": {
- "kind": "",
- "spec": {...},
- }
- }
-```
-
-The `PanelKind` consists of:
-
-- kind: "Panel"
-- spec: [PanelSpec](#panelspec)
-
-## `PanelSpec`
-
-The following table explains the usage of the panel element JSON fields:
-
-
-
-| Name | Usage |
-| ------------ | --------------------------------------------------------------------- |
-| data | `QueryGroupKind`, which includes queries and transformations. Consists of:- kind: "QueryGroup"
- spec: [QueryGroupSpec](#querygroupspec)
|
-| description | The panel description. |
-| id | The panel ID. |
-| links | Links with references to other dashboards or external websites. |
-| title | The panel title. |
-| vizConfig | `VizConfigKind`. Includes visualization type, field configuration options, and all other visualization options. Consists of:- kind: string. Plugin ID.
- spec: [VizConfigSpec](#vizconfigspec)
|
-| transparent? | bool. Controls whether or not the panel background is transparent. |
-
-
-
-### `QueryGroupSpec`
-
-
-
-| Name | Usage |
-| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| queries | `PanelQueryKind`. Consists of:- kind: PanelQuery
- spec: [PanelQuerySpec](#panelqueryspec)
|
-| transformations | `TransformationKind`. Consists of:- kind: string. The transformation ID.
- spec: [DataTransformerConfig](#datatransformerconfig)
|
-| queryOptions | [`QueryOptionsSpec`](#queryoptionsspec) |
-
-
-
-#### `PanelQuerySpec`
-
-| Name | Usage |
-| ----------- | --------------------------------- |
-| query | [`DataQueryKind`](#dataquerykind) |
-| datasource? | [`DataSourceRef`](#datasourceref) |
-
-##### `DataQueryKind`
-
-| Name | Type |
-| ---- | ------ |
-| kind | string |
-| spec | string |
-
-##### `DataSourceRef`
-
-| Name | Usage |
-| ----- | ---------------------------------- |
-| type? | string. The plugin type-id. |
-| uid? | The specific data source instance. |
-
-#### `DataTransformerConfig`
-
-Transformations allow you to manipulate data returned by a query before the system applies a visualization.
-Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, or use the output of one transformation as the input to another transformation.
-
-
-
-| Name | Usage |
-| --------- | ------------------------------------------- |
-| id | string. Unique identifier of transformer. |
-| disabled? | bool. Disabled transformations are skipped. |
-| filter? | [`MatcherConfig`](#matcherconfig). Optional frame matcher. When missing it will be applied to all results. |
-| topic? | `DataTopic`. Where to pull `DataFrames` from as input to transformation. Options are: `series`, `annotations`, and `alertStates`. |
-| options | Options to be passed to the transformer. Valid options depend on the transformer id. |
-
-
-
-##### `MatcherConfig`
-
-Matcher is a predicate configuration.
-Based on the configuration a set of field or values, it's filtered to apply an override or transformation.
-It comes with in id (to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.
-
-| Name | Usage |
-| -------- | -------------------------------------------------------------------------------------- |
-| id | string. The matcher id. This is used to find the matcher implementation from registry. |
-| options? | The matcher options. This is specific to the matcher implementation. |
-
-#### `QueryOptionsSpec`
-
-| Name | Type |
-| ----------------- | ------- |
-| timeFrom? | string |
-| maxDataPoints? | integer |
-| timeShift? | string |
-| queryCachingTTL? | integer |
-| interval? | string |
-| cacheTimeout? | string |
-| hideTimeOverride? | bool |
-
-### `VizConfigSpec`
-
-| Name | Type/Definition |
-| ------------- | --------------------------------------- |
-| pluginVersion | string |
-| options | string |
-| fieldConfig | [FieldConfigSource](#fieldconfigsource) |
-
-#### `FieldConfigSource`
-
-The data model used in Grafana, namely the _data frame_, is a columnar-oriented table structure that unifies both time series and table query results.
-Each column within this structure is called a field.
-A field can represent a single time series or table column.
-Field options allow you to change how the data is displayed in your visualizations.
-
-
-
-| Name | Type/Definition |
-| ---------- | ------------------------------------- |
-| defaults | [`FieldConfig`](#fieldconfig). Defaults are the options applied to all fields. |
-| overrides | The options applied to specific fields overriding the defaults. |
-| matcher | [`MatcherConfig`](#matcherconfig). Optional frame matcher. When missing it will be applied to all results. |
-| properties | `DynamicConfigValue`. Consists of: |
-
-
-
-##### `FieldConfig`
-
-
-
-| Name | Type/Definition |
-| ------------------ | --------------------------------------- |
-| displayName? | string. The display value for this field. This supports template variables where empty is auto. |
-| displayNameFromDS? | string. This can be used by data sources that return an explicit naming structure for values and labels. When this property is configured, this value is used rather than the default naming strategy. |
-| description? | string. Human readable field metadata. |
-| path? | string. An explicit path to the field in the data source. When the frame meta includes a path, this will default to `${frame.meta.path}/${field.name}`. When defined, this value can be used as an identifier within the data source scope, and may be used to update the results. |
-| writeable? | bool. True if the data source can write a value to the path. Auth/authz are supported separately. |
-| filterable? | bool. True if the data source field supports ad-hoc filters. |
-| unit? | string. Unit a field should use. The unit you select is applied to all fields except time. You can use the unit's ID available in Grafana or a custom unit. [Available units in Grafana](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts). As custom units, you can use the following formats:- `suffix:` for custom unit that should go after value.
- `prefix:` for custom unit that should go before value.
- `time:` for custom date time formats type for example
- `time:YYYY-MM-DD`
- `si:` for custom SI units. For example: `si: mF`. You can specify both a unit and the source data scale, so if your source data is represented as milli (thousands of) something, prefix the unit with that SI scale character.
- `count:` for a custom count unit.
- `currency:` for custom a currency unit.
|
-| decimals? | number. Specify the number of decimals Grafana includes in the rendered value. If you leave this field blank, Grafana automatically truncates the number of decimals based on the value. For example 1.1234 will display as 1.12 and 100.456 will display as 100. To display all decimals, set the unit to `string`. |
-| min? | number. The minimum value used in percentage threshold calculations. Leave empty for auto calculation based on all series and fields. |
-| max? | number. The maximum value used in percentage threshold calculations. Leave empty for auto calculation based on all series and fields. |
-| mappings? | `[...ValueMapping]`. Convert input values into a display string. Options are: [`ValueMap`](#valuemap), [`RangeMap`](#rangemap), [`RegexMap`](#rangemap), [`SpecialValueMap`](#specialvaluemap). |
-| thresholds? | `ThresholdsConfig`. Map numeric values to states. Consists of:- `mode` - `ThresholdsMode`. Options are: `absolute` and `percentage`.
- `steps` - `[...Threshold]`
|
-| color? | [`FieldColor`](#fieldcolor). Panel color configuration. |
-| links? | `[...]`. The behavior when clicking a result. |
-| noValue? | string. Alternative to an empty string. |
-| custom? | `{...}`. Specified by the `FieldConfig` field in panel plugin schemas. |
-
-
-
-###### `ValueMap`
-
-Maps text values to a color or different display text and color.
-For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
-
-
-
-| Name | Usage |
-| ------- | -------- |
-| type | `MappingType` & "value". `MappingType` options are: `value`, `range`, `regex`, and `special`. |
-| options | string. [`ValueMappingResult`](#valuemappingresult). Map with ``: `ValueMappingResult`. For example: `{ "10": { text: "Perfection!", color: "green" } }`. |
-
-
-
-###### `RangeMap`
-
-Maps numerical ranges to a display text and color.
-For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
-
-
-
-| Name | Usage |
-| ------- | ---------------------------------------------------------------------------------------------------- |
-| type | `MappingType` & "range". `MappingType` options are: `value`, `range`, `regex`, and `special`. |
-| options | Range to match against and the result to apply when the value is within the range. Spec:- `from` - `float64` or `null`. Min value of the range. It can be null which means `-Infinity`.
- `to` - `float64` or `null`. Max value of the range. It can be null which means `+Infinity`.
- `result` - [`ValueMappingResult`](#valuemappingresult) |
-
-
-
-###### `RegexMap`
-
-Maps regular expressions to replacement text and a color.
-For example, if a value is `www.example.com`, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
-
-
-
-| Name | Usage |
-| ------- | --------------------------------------------------------------------------------------------- |
-| type | `MappingType` & "regex". `MappingType` options are: `value`, `range`, `regex`, and `special`. |
-| options | Regular expression to match against and the result to apply when the value matches the regex. Spec:
- `pattern` - string. Regular expression to match against.
- `result` - [`ValueMappingResult`](#valuemappingresult) |
-
-
-
-###### `SpecialValueMap`
-
-Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.
-See `SpecialValueMatch` in the following table to see the list of special values.
-For example, you can configure a special value mapping so that null values appear as N/A.
-
-
-
-| Name | Usage |
-| ------- | ----------------------------------------------------------------------------------------------- |
-| type | `MappingType` & "special". `MappingType` options are: `value`, `range`, `regex`, and `special`. |
-| options | Spec:
- `match` - `SpecialValueMatch`. Special value to match against. Types are:
- `result` - [`ValueMappingResult`](#valuemappingresult) |
-
-
-
-###### `ValueMappingResult`
-
-Result used as replacement with text and color when the value matches.
-
-| Name | Usage |
-| ----- | ----------------------------------------------------------------------------- |
-| text | string. Text to display when the value matches. |
-| color | string. Color to use when the value matches. |
-| icon | string. Icon to display when the value matches. Only specific visualizations. |
-| index | int32. Position in the mapping array. Only used internally. |
-
-###### `FieldColor`
-
-Map a field to a color.
-
-
-
-| Name | Usage |
-| ----------- | -------------------------------------------------------------------- |
-| mode | [`FieldColorModeId`](#fieldcolormodeid). The main color scheme mode. |
-| FixedColor? | string. The fixed color value for fixed or shades color modes. |
-| seriesBy? | `FieldColorSeriesByMode`. Some visualizations need to know how to assign a series color from by value color schemes. Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value. Options are: `min`, `max`, and `last`. |
-
-
-
-###### `FieldColorModeId`
-
-Color mode for a field.
-You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
-Continuous color interpolates a color using the percentage of a value relative to min and max.
-Accepted values are:
-
-
-
-| Name | Description |
-| --- | ---- |
-| thresholds | From thresholds. Informs Grafana to take the color from the matching threshold. |
-| palette-classic | Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for graphs and pie charts and other categorical data visualizations. |
-| palette-classic-by-name | Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations |
-| continuous-GrYlRd | Continuous Green-Yellow-Red palette mode |
-| continuous-RdYlGr | Continuous Red-Yellow-Green palette mode |
-| continuous-BlYlRd | Continuous Blue-Yellow-Red palette mode |
-| continuous-YlRd | Continuous Yellow-Red palette mode |
-| continuous-BlPu | Continuous Blue-Purple palette mode |
-| continuous-YlBl | Continuous Yellow-Blue palette mode |
-| continuous-blues | Continuous Blue palette mode |
-| continuous-reds | Continuous Red palette mode |
-| continuous-greens | Continuous Green palette mode |
-| continuous-purples | Continuous Purple palette mode |
-| shades | Shades of a single color. Specify a single color, useful in an override rule. |
-| fixed | Fixed color mode. Specify a single color, useful in an override rule. |
-
-
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/timesettings-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/timesettings-schema.md
deleted file mode 100644
index 8db14212740..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/timesettings-schema.md
+++ /dev/null
@@ -1,87 +0,0 @@
----
-description: A reference for the JSON timesettings schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - time settings
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: timesettings schema
-title: timesettings
-weight: 600
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/timesettings-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/timesettings-schema/ # /docs/grafana/next/observability-as-code/schema-v2/timesettings-schema/
----
-
-# `timeSettings`
-
-The `TimeSettingsSpec` defines the default time configuration for the time picker and the refresh picker for the specific dashboard.
-
-Following is the JSON for default time settings:
-
-```json
- "timeSettings": {
- "autoRefresh": "",
- "autoRefreshIntervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ],
- "fiscalYearStartMonth": 0,
- "from": "now-6h",
- "hideTimepicker": false,
- "timezone": "browser",
- "to": "now"
- },
-```
-
-`timeSettings` consists of:
-
-- [TimeSettingsSpec](#timesettingsspec)
-
-## `TimeSettingsSpec`
-
-The following table explains the usage of the time settings JSON fields:
-
-
-
-| Name | Usage |
-| ---- | ----- |
-| timezone? | string. Timezone of dashboard. Accepted values are IANA TZDB zone ID, `browser`, or `utc`. Default is `browser`. |
-| from | string. Start time range for dashboard. Accepted values are relative time strings like `now-6h` or absolute time strings like `2020-07-10T08:00:00.000Z`. Default is `now-6h`. |
-| to | string. End time range for dashboard. Accepted values are relative time strings like `now-6h` or absolute time strings like `2020-07-10T08:00:00.000Z`. Default is `now`. |
-| autoRefresh | string. Refresh rate of dashboard. Represented by interval string. For example: `5s`, `1m`, `1h`, `1d`. No default. In schema v1: `refresh`. |
-| autoRefreshIntervals | string. Interval options available in the refresh picker drop-down menu. The default array is `["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]`. |
-|quickRanges? | Selectable options available in the time picker drop-down menu. Has no effect on provisioned dashboard. Defined in the [`TimeRangeOption`](#timerangeoption) spec. In schema v1: `timepicker.quick_ranges`, not exposed in the UI. |
-| hideTimepicker | bool. Whether or not the time picker is visible. Default is `false`. In schema v1: `timepicker.hidden`. |
-| weekStart? | Day when the week starts. Expressed by the name of the day in lowercase. For example: `monday`. Options are `saturday`, `monday`, and `sunday`. |
-| fiscalYearStartMonth | The month that the fiscal year starts on. `0` = January, `11` = December |
-| nowDelay? | string. Override the "now" time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. In schema v1: `timepicker.nowDelay`. |
-
-
-
-### `TimeRangeOption`
-
-The following table explains the usage of the time range option JSON fields:
-
-| Name | Usage |
-| ------- | ---------------------------------- |
-| display | string. Default is `Last 6 hours`. |
-| from | string. Default is `now-6h`. |
-| to | string. Default is `now`. |
diff --git a/docs/sources/as-code/observability-as-code/schema-v2/variables-schema.md b/docs/sources/as-code/observability-as-code/schema-v2/variables-schema.md
deleted file mode 100644
index 549478692f1..00000000000
--- a/docs/sources/as-code/observability-as-code/schema-v2/variables-schema.md
+++ /dev/null
@@ -1,501 +0,0 @@
----
-description: A reference for the JSON variables schema used with Observability as Code.
-keywords:
- - configuration
- - as code
- - as-code
- - dashboards
- - git integration
- - git sync
- - github
- - variables
-labels:
- products:
- - cloud
- - enterprise
- - oss
-menuTitle: variables schema
-title: variables
-weight: 700
-canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/schema-v2/variables-schema/
-aliases:
- - ../../../observability-as-code/schema-v2/variables-schema/ # /docs/grafana/next/observability-as-code/schema-v2/variables-schema/
----
-
-# `variables`
-
-The available variable types described in the following sections:
-
-- [QueryVariableKind](#queryvariablekind)
-- [TextVariableKind](#textvariablekind)
-- [ConstantVariableKind](#constantvariablekind)
-- [DatasourceVariableKind](#datasourcevariablekind)
-- [IntervalVariableKind](#intervalvariablekind)
-- [CustomVariableKind](#customvariablekind)
-- [SwitchVariableKind](#switchvariablekind)
-- [GroupByVariableKind](#groupbyvariablekind)
-- [AdhocVariableKind](#adhocvariablekind)
-
-## `QueryVariableKind`
-
-Following is the JSON for a default query variable:
-
-```json
- "variables": [
- {
- "kind": "QueryVariable",
- "spec": {
- "current": {
- "text": "",
- "value": ""
- },
- "hide": "dontHide",
- "includeAll": false,
- "multi": false,
- "name": "",
- "options": [],
- "query": defaultDataQueryKind(),
- "refresh": "never",
- "regex": "",
- "skipUrlSync": false,
- "sort": "disabled"
- }
- }
- ]
-```
-
-`QueryVariableKind` consists of:
-
-- kind: "QueryVariable"
-- spec: [QueryVariableSpec](#queryvariablespec)
-
-### `QueryVariableSpec`
-
-The following table explains the usage of the query variable JSON fields:
-
-
-
-| Name | Usage |
-| ------------ | ---------------------------------------------- |
-| name | string. Name of the variable. |
-| current | "Text" and a "value" or [`VariableOption`](#variableoption) |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| refresh | `VariableRefresh`. Options are `never`, `onDashboardLoad`, and `onTimeChanged`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-| datasource? | [`DataSourceRef`](#datasourceref) |
-| query | `DataQueryKind`. Consists of: |
-| regex | string |
-| sort | `VariableSort`. Options are:
- disabled
- alphabeticalAsc
- alphabeticalDesc
- numericalAsc
- numericalDesc
- alphabeticalCaseInsensitiveAsc
- alphabeticalCaseInsensitiveDesc
- naturalAsc
- naturalDesc
|
-| definition? | string |
-| options | [`VariableOption`](#variableoption) |
-| multi | bool. Default is `false`. |
-| includeAll | bool. Default is `false`. |
-| allValue? | string |
-| placeholder? | string |
-
-
-
-#### `VariableOption`
-
-| Name | Usage |
-| -------- | -------------------------------------------- |
-| selected | bool. Whether or not the option is selected. |
-| text | string. Text to be displayed for the option. |
-| value | string. Value of the option. |
-
-#### `DataSourceRef`
-
-| Name | Usage |
-| ----- | ---------------------------------- |
-| type? | string. The plugin type-id. |
-| uid? | The specific data source instance. |
-
-## `TextVariableKind`
-
-Following is the JSON for a default text variable:
-
-```json
- "variables": [
- {
- "kind": "TextVariable",
- "spec": {
- "current": {
- "text": "",
- "value": ""
- },
- "hide": "dontHide",
- "name": "",
- "query": "",
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`TextVariableKind` consists of:
-
-- kind: TextVariableKind
-- spec: [TextVariableSpec](#textvariablespec)
-
-### `TextVariableSpec`
-
-The following table explains the usage of the query variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| current | "Text" and a "value" or `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| query | string |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-
-## `ConstantVariableKind`
-
-Following is the JSON for a default constant variable:
-
-```json
- "variables": [
- {
- "kind": "ConstantVariable",
- "spec": {
- "current": {
- "text": "",
- "value": ""
- },
- "hide": "hideVariable",
- "name": "",
- "query": "",
- "skipUrlSync": true
- }
- }
- ]
-```
-
-`ConstantVariableKind` consists of:
-
-- kind: "ConstantVariable"
-- spec: [ConstantVariableSpec](#constantvariablespec)
-
-### `ConstantVariableSpec`
-
-The following table explains the usage of the constant variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| query | string |
-| current | "Text" and a "value" or `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-
-## `DatasourceVariableKind`
-
-Following is the JSON for a default data source variable:
-
-```json
- "variables": [
- {
- "kind": "DatasourceVariable",
- "spec": {
- "current": {
- "text": "",
- "value": ""
- },
- "hide": "dontHide",
- "includeAll": false,
- "multi": false,
- "name": "",
- "options": [],
- "pluginId": "",
- "refresh": "never",
- "regex": "",
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`DatasourceVariableKind` consists of:
-
-- kind: "DatasourceVariable"
-- spec: [DatasourceVariableSpec](#datasourcevariablespec)
-
-### `DatasourceVariableSpec`
-
-The following table explains the usage of the data source variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| pluginId | string |
-| refresh | `VariableRefresh`. Options are `never`, `onDashboardLoad`, and `onTimeChanged`. |
-| regex | string |
-| current | `Text` and a `value` or `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| options | `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| multi | bool. Default is `false`. |
-| includeAll | bool. Default is `false`. |
-| allValue? | string |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-
-## `IntervalVariableKind`
-
-Following is the JSON for a default interval variable:
-
-```json
- "variables": [
- {
- "kind": "IntervalVariable",
- "spec": {
- "auto": false,
- "auto_count": 0,
- "auto_min": "",
- "current": {
- "text": "",
- "value": ""
- },
- "hide": "dontHide",
- "name": "",
- "options": [],
- "query": "",
- "refresh": "never",
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`IntervalVariableKind` consists of:
-
-- kind: "IntervalVariable"
-- spec: [IntervalVariableSpec](#intervalvariablespec)
-
-### `IntervalVariableSpec`
-
-The following table explains the usage of the interval variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| query | string |
-| current | `Text` and a `value` or `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| options | `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| auto | bool. Default is `false`. |
-| auto_count | integer. Default is `0`. |
-| refresh | `VariableRefresh`. Options are `never`, `onDashboardLoad`, and `onTimeChanged`. |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false` |
-| description? | string |
-
-## `CustomVariableKind`
-
-Following is the JSON for a default custom variable:
-
-```json
- "variables": [
- {
- "kind": "CustomVariable",
- "spec": {
- "current": defaultVariableOption(),
- "hide": "dontHide",
- "includeAll": false,
- "multi": false,
- "name": "",
- "options": [],
- "query": "",
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`CustomVariableKind` consists of:
-
-- kind: "CustomVariable"
-- spec: [CustomVariableSpec](#customvariablespec)
-
-### `CustomVariableSpec`
-
-The following table explains the usage of the custom variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| query | string |
-| current | `Text` and a `value` or `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| options | `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| multi | bool. Default is `false`. |
-| includeAll | bool. Default is `false`. |
-| allValue? | string |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-
-## `SwitchVariableKind`
-
-Following is the JSON for a default switch variable:
-
-```json
- "variables": [
- {
- "kind": "SwitchVariable",
- "spec": {
- "current": "false",
- "enabledValue": "true",
- "disabledValue": "false",
- "hide": "dontHide",
- "name": "",
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`SwitchVariableKind` consists of:
-
-- kind: "SwitchVariable"
-- spec: [SwitchVariableSpec](#switchvariablespec)
-
-### `SwitchVariableSpec`
-
-The following table explains the usage of the switch variable JSON fields:
-
-
-
-| Name | Usage |
-| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| current | string. Current value of the switch variable (either `enabledValue` or `disabledValue`). |
-| enabledValue | string. Value when the switch is in the enabled state. |
-| disabledValue | string. Value when the switch is in the disabled state. |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-
-
-
-## `GroupByVariableKind`
-
-Following is the JSON for a default group by variable:
-
-```json
- "variables": [
- {
- "kind": "GroupByVariable",
- "spec": {
- "current": {
- "text": [
- ""
- ],
- "value": [
- ""
- ]
- },
- "datasource": {},
- "hide": "dontHide",
- "multi": false,
- "name": "",
- "options": [],
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`GroupByVariableKind` consists of:
-
-- kind: "GroupByVariable"
-- spec: [GroupByVariableSpec](#groupbyvariablespec)
-
-### `GroupByVariableSpec`
-
-The following table explains the usage of the group by variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable |
-| datasource? | `DataSourceRef`. Refer to the [`DataSourceRef` definition](#datasourceref) under `QueryVariableKind`. |
-| current | `Text` and a `value` or `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| options | `VariableOption`. Refer to the [`VariableOption` definition](#variableoption) under `QueryVariableKind`. |
-| multi | bool. Default is `false`. |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string. |
-
-## `AdhocVariableKind`
-
-Following is the JSON for a default ad hoc variable:
-
-```json
- "variables": [
- {
- "kind": "AdhocVariable",
- "spec": {
- "baseFilters": [],
- "defaultKeys": [],
- "filters": [],
- "hide": "dontHide",
- "name": "",
- "skipUrlSync": false
- }
- }
- ]
-```
-
-`AdhocVariableKind` consists of:
-
-- kind: "AdhocVariable"
-- spec: [AdhocVariableSpec](#adhocvariablespec)
-
-### `AdhocVariableSpec`
-
-The following table explains the usage of the ad hoc variable JSON fields:
-
-| Name | Usage |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
-| name | string. Name of the variable. |
-| datasource? | `DataSourceRef`. Consists of:- type? - string. The plugin type-id.
- uid? - string. The specific data source instance.
|
-| baseFilters | [AdHocFilterWithLabels](#adhocfilterswithlabels) |
-| filters | [AdHocFilterWithLabels](#adhocfilterswithlabels) |
-| defaultKeys | [MetricFindValue](#metricfindvalue) |
-| label? | string |
-| hide | `VariableHide`. Options are: `dontHide`, `hideLabel`, and `hideVariable`. |
-| skipUrlSync | bool. Default is `false`. |
-| description? | string |
-
-#### `AdHocFiltersWithLabels`
-
-The following table explains the usage of the ad hoc variable with labels JSON fields:
-
-| Name | Type |
-| ------------ | ------------- |
-| key | string |
-| operator | string |
-| value | string |
-| values? | `[...string]` |
-| keyLabel | string |
-| valueLabels? | `[...string]` |
-| forceEdit? | bool |
-
-#### `MetricFindValue`
-
-The following table explains the usage of the metric find value JSON fields:
-
-| Name | Type |
-| ----------- | ---------------- |
-| text | string |
-| value? | string or number |
-| group? | string |
-| expandable? | bool |
diff --git a/docs/sources/datasources/google-cloud-monitoring/_index.md b/docs/sources/datasources/google-cloud-monitoring/_index.md
index 27bc7b390cd..d5412c33ef7 100644
--- a/docs/sources/datasources/google-cloud-monitoring/_index.md
+++ b/docs/sources/datasources/google-cloud-monitoring/_index.md
@@ -103,10 +103,11 @@ To configure basic settings for the data source, complete the following steps:
1. Set the data source's basic configuration options:
- | Name | Description |
- | ----------- | ------------------------------------------------------------------------ |
- | **Name** | Sets the name you use to refer to the data source in panels and queries. |
- | **Default** | Sets whether the data source is pre-selected for new panels. |
+ | Name | Description |
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+ | **Name** | Sets the name you use to refer to the data source in panels and queries. |
+ | **Default** | Sets whether the data source is pre-selected for new panels. |
+ | **Universe Domain** | The universe domain to connect to. For more information, refer to [Documentation on universe domains](https://docs.cloud.google.com/python/docs/reference/monitoring/latest/google.cloud.monitoring_v3.services.service_monitoring_service.ServiceMonitoringServiceAsyncClient#google_cloud_monitoring_v3_services_service_monitoring_service_ServiceMonitoringServiceAsyncClient_universe_domain). Defaults to `googleapis.com`. |
### Provision the data source
@@ -129,6 +130,7 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
+ universeDomain: googleapis.com
secureJsonData:
privateKey: |
-----BEGIN PRIVATE KEY-----
@@ -152,6 +154,7 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
+ universeDomain: googleapis.com
privateKeyPath: /etc/secrets/gce.pem
```
@@ -166,6 +169,7 @@ datasources:
access: proxy
jsonData:
authenticationType: gce
+ universeDomain: googleapis.com
```
## Import pre-configured dashboards
diff --git a/docs/sources/datasources/mssql/_index.md b/docs/sources/datasources/mssql/_index.md
index a5e00da6dcb..b7ea159e972 100644
--- a/docs/sources/datasources/mssql/_index.md
+++ b/docs/sources/datasources/mssql/_index.md
@@ -99,12 +99,27 @@ refs:
destination: /docs/grafana//administration/data-source-management/#query-and-resource-caching
- pattern: /docs/grafana-cloud/
destination: /docs/grafana//administration/data-source-management/#query-and-resource-caching
+ mssql-troubleshoot:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mssql/troubleshooting/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mssql/troubleshooting/
+ postgres:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/postgres/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/postgres/
+ mysql:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mysql/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mysql/
---
# Microsoft SQL Server (MSSQL) data source
Grafana ships with built-in support for Microsoft SQL Server (MSSQL).
-You can query and visualize data from any Microsoft SQL Server 2005 or newer, including the Microsoft Azure SQL Database.
+You can query and visualize data from any Microsoft SQL Server 2005 or newer, including Microsoft Azure SQL Database.
Use this data source to create dashboards, explore SQL data, and monitor MSSQL-based workloads in real time.
@@ -113,10 +128,33 @@ The following documentation helps you get started working with the Microsoft SQL
- [Configure the Microsoft SQL Server data source](ref:configure-mssql-data-source)
- [Microsoft SQL Server query editor](ref:mssql-query-editor)
- [Microsoft SQL Server template variables](ref:mssql-template-variables)
+- [Troubleshoot Microsoft SQL Server data source issues](ref:mssql-troubleshoot)
-## Get the most out of the data source
+## Supported versions
-After installing and configuring the Microsoft SQL Server data source, you can:
+This data source supports the following Microsoft SQL Server versions:
+
+- Microsoft SQL Server 2005 and newer
+- Microsoft Azure SQL Database
+- Azure SQL Managed Instance
+
+Grafana recommends using the latest available service pack for your SQL Server version for optimal compatibility.
+
+## Key capabilities
+
+The Microsoft SQL Server data source supports:
+
+- **Time series queries:** Visualize metrics over time using the built-in time grouping macros.
+- **Table queries:** Display query results in table format for any valid SQL query.
+- **Template variables:** Create dynamic dashboards with variable-driven queries.
+- **Annotations:** Overlay events from SQL Server on your dashboard graphs.
+- **Alerting:** Create alerts based on SQL Server query results.
+- **Stored procedures:** Execute stored procedures and visualize results.
+- **Macros:** Simplify queries with built-in macros for time filtering and grouping.
+
+## Additional resources
+
+After configuring the Microsoft SQL Server data source, you can:
- Create a wide variety of [visualizations](ref:visualizations)
- Configure and use [templates and variables](ref:variables)
@@ -124,3 +162,8 @@ After installing and configuring the Microsoft SQL Server data source, you can:
- Add [annotations](ref:annotate-visualizations)
- Set up [alerting](ref:alerting)
- Optimize performance with [query caching](ref:query-caching)
+
+## Related data sources
+
+- [PostgreSQL](ref:postgres) - For PostgreSQL databases.
+- [MySQL](ref:mysql) - For MySQL and MariaDB databases.
diff --git a/docs/sources/datasources/mssql/configure/index.md b/docs/sources/datasources/mssql/configure/index.md
index 7ce6398f1cc..f41deeb51dd 100644
--- a/docs/sources/datasources/mssql/configure/index.md
+++ b/docs/sources/datasources/mssql/configure/index.md
@@ -89,6 +89,26 @@ refs:
destination: /docs/grafana//setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana
- pattern: /docs/grafana-cloud/
destination: /docs/grafana//setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana
+ mssql-query-editor:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mssql/query-editor/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mssql/query-editor/
+ mssql-template-variables:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mssql/template-variables/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mssql/template-variables/
+ alerting:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//alerting/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana-cloud/alerting-and-irm/alerting/
+ mssql-troubleshoot:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mssql/troubleshooting/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mssql/troubleshooting/
---
# Configure the Microsoft SQL Server data source
@@ -97,13 +117,28 @@ This document provides instructions for configuring the Microsoft SQL Server dat
## Before you begin
-- Grafana comes with a built-in MSSQL data source plugin, eliminating the need to install a plugin.
+Before configuring the Microsoft SQL Server data source, ensure you have the following:
-- You must have the `Organization administrator` role to configure the MSSQL data source. Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system.
+- **Grafana permissions:** You must have the `Organization administrator` role to configure data sources. Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system.
-- Familiarize yourself with your MSSQL security configuration and gather any necessary security certificates and client keys.
+- **A running SQL Server instance:** Microsoft SQL Server 2005 or newer, Azure SQL Database, or Azure SQL Managed Instance.
-- Verify that data from MSSQL is being written to your Grafana instance.
+- **Network access:** Grafana must be able to reach your SQL Server. The default port is `1433`.
+
+- **Authentication credentials:** Depending on your authentication method, you need one of:
+ - SQL Server login credentials (username and password).
+ - Windows/Kerberos credentials and configuration (not supported in Grafana Cloud).
+ - Azure Entra ID app registration or managed identity.
+
+- **Security certificates:** If using encrypted connections, gather any necessary TLS/SSL certificates.
+
+{{< admonition type="note" >}}
+Grafana ships with a built-in Microsoft SQL Server data source plugin. No additional installation is required.
+{{< /admonition >}}
+
+{{< admonition type="tip" >}}
+**Grafana Cloud users:** If your SQL Server is in a private network, you can configure [Private data source connect](ref:private-data-source-connect) to establish connectivity.
+{{< /admonition >}}
## Add the MSSQL data source
@@ -382,3 +417,48 @@ datasources:
secureJsonData:
password: 'Password!'
```
+
+### Configure with Terraform
+
+You can configure the Microsoft SQL Server data source using [Terraform](https://www.terraform.io/) with the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs).
+
+For more information about provisioning resources with Terraform, refer to the [Grafana as code using Terraform](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/terraform/) documentation.
+
+#### Terraform example
+
+The following example creates a basic Microsoft SQL Server data source:
+
+```hcl
+resource "grafana_data_source" "mssql" {
+ name = "MSSQL"
+ type = "mssql"
+ url = "localhost:1433"
+ user = "grafana"
+
+ json_data_encoded = jsonencode({
+ database = "grafana"
+ maxOpenConns = 100
+ maxIdleConns = 100
+ maxIdleConnsAuto = true
+ connMaxLifetime = 14400
+ connectionTimeout = 0
+ encrypt = "false"
+ })
+
+ secure_json_data_encoded = jsonencode({
+ password = "Password!"
+ })
+}
+```
+
+For all available configuration options, refer to the [Grafana provider data source resource documentation](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source).
+
+## Next steps
+
+After configuring your Microsoft SQL Server data source, you can:
+
+- [Write queries](ref:mssql-query-editor) using the query editor to explore and visualize your data
+- [Create template variables](ref:mssql-template-variables) to build dynamic, reusable dashboards
+- [Add annotations](ref:annotate-visualizations) to overlay SQL Server events on your graphs
+- [Set up alerting](ref:alerting) to create alert rules based on your SQL Server data
+- [Troubleshoot issues](ref:mssql-troubleshoot) if you encounter problems with your data source
diff --git a/docs/sources/datasources/mssql/troubleshooting/index.md b/docs/sources/datasources/mssql/troubleshooting/index.md
new file mode 100644
index 00000000000..a62f3eb59e1
--- /dev/null
+++ b/docs/sources/datasources/mssql/troubleshooting/index.md
@@ -0,0 +1,333 @@
+---
+description: Troubleshoot common problems with the Microsoft SQL Server data source in Grafana
+keywords:
+ - grafana
+ - MSSQL
+ - Microsoft
+ - SQL
+ - troubleshooting
+ - errors
+labels:
+ products:
+ - cloud
+ - enterprise
+ - oss
+menuTitle: Troubleshooting
+title: Troubleshoot Microsoft SQL Server data source issues
+weight: 400
+refs:
+ configure-mssql-data-source:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mssql/configure/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mssql/configure/
+ mssql-query-editor:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana//datasources/mssql/query-editor/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana//datasources/mssql/query-editor/
+ private-data-source-connect:
+ - pattern: /docs/grafana/
+ destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/
+ - pattern: /docs/grafana-cloud/
+ destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/
+---
+
+# Troubleshoot Microsoft SQL Server data source issues
+
+This document provides solutions to common issues you may encounter when configuring or using the Microsoft SQL Server (MSSQL) data source in Grafana.
+
+## Connection errors
+
+These errors occur when Grafana cannot establish or maintain a connection to the Microsoft SQL Server.
+
+### Unable to connect to the server
+
+**Error message:** "Unable to open tcp connection" or "dial tcp: connection refused"
+
+**Cause:** Grafana cannot establish a network connection to the SQL Server.
+
+**Solution:**
+
+1. Verify that the SQL Server is running and accessible.
+1. Check that the host and port are correct in the data source configuration. The default SQL Server port is `1433`.
+1. Ensure there are no firewall rules blocking the connection between Grafana and SQL Server.
+1. Verify that SQL Server is configured to allow remote connections.
+1. For Grafana Cloud, ensure you have configured [Private data source connect](ref:private-data-source-connect) if your SQL Server instance is not publicly accessible.
+
+### Connection timeout
+
+**Error message:** "Connection timed out" or "I/O timeout"
+
+**Cause:** The connection to SQL Server timed out before receiving a response.
+
+**Solution:**
+
+1. Check the network latency between Grafana and SQL Server.
+1. Verify that SQL Server is not overloaded or experiencing performance issues.
+1. Increase the **Connection timeout** setting in the data source configuration under **Additional settings**.
+1. Check if any network devices (load balancers, proxies) are timing out the connection.
+
+### Encryption-related connection failures
+
+**Error message:** "TLS handshake failed" or "certificate verify failed"
+
+**Cause:** There is a mismatch between the encryption settings in Grafana and what the SQL Server supports or requires.
+
+**Solution:**
+
+1. For older versions of SQL Server (2008, 2008R2), set the **Encrypt** option to **Disable** or **False** in the data source configuration.
+1. Verify that the SQL Server has a valid SSL certificate if encryption is enabled.
+1. Check that the certificate is trusted by the Grafana server.
+1. Ensure you're using the latest available service pack for your SQL Server version for optimal compatibility.
+
+### Named instance connection issues
+
+**Error message:** "Cannot connect to named instance" or connection fails when using instance name
+
+**Cause:** Grafana cannot resolve the SQL Server named instance.
+
+**Solution:**
+
+1. Use the format `hostname\instancename` or `hostname\instancename,port` in the **Host** field.
+1. Verify that the SQL Server Browser service is running on the SQL Server machine.
+1. If the Browser service is unavailable, specify the port number directly: `hostname,port`.
+1. Check that UDP port 1434 is open if using the SQL Server Browser service.
+
+## Authentication errors
+
+These errors occur when there are issues with authentication credentials or permissions.
+
+### Login failed for user
+
+**Error message:** "Login failed for user 'username'" or "Authentication failed"
+
+**Cause:** The authentication credentials are invalid or the user doesn't have permission to access the database.
+
+**Solution:**
+
+1. Verify that the username and password are correct.
+1. Check that the user exists in SQL Server and is enabled.
+1. Ensure the user has access to the specified database.
+1. For Windows Authentication, verify that the credentials are in the correct format (`DOMAIN\User`).
+1. Check that the SQL Server authentication mode allows the type of login you're using (SQL Server Authentication, Windows Authentication, or Mixed Mode).
+
+### Access denied to database
+
+**Error message:** "Cannot open database 'dbname' requested by the login"
+
+**Cause:** The authenticated user doesn't have permission to access the specified database.
+
+**Solution:**
+
+1. Verify that the database name is correct in the data source configuration.
+1. Ensure the user is mapped to the database with appropriate permissions.
+1. Grant at least `SELECT` permission on the required tables:
+
+ ```sql
+ USE [your_database]
+ GRANT SELECT ON dbo.YourTable TO [your_user]
+ ```
+
+1. Check that the user doesn't have any conflicting permissions from the public role.
+
+### Windows Authentication (Kerberos) issues
+
+**Error message:** "Kerberos authentication failed" or "Cannot initialize Kerberos"
+
+**Cause:** Kerberos configuration is incorrect or incomplete.
+
+**Solution:**
+
+1. Verify that the Kerberos configuration file (`krb5.conf`) path is correct in the data source settings.
+1. For keytab authentication, ensure the keytab file exists and is readable by Grafana.
+1. Check that the realm and KDC settings are correct in the Kerberos configuration.
+1. Verify DNS is correctly resolving the KDC servers.
+1. Ensure the service principal name (SPN) is registered for the SQL Server instance.
+
+{{< admonition type="note" >}}
+Kerberos authentication is not supported in Grafana Cloud.
+{{< /admonition >}}
+
+### Azure Entra ID authentication errors
+
+**Error message:** "AADSTS error codes" or "Azure AD authentication failed"
+
+**Cause:** Azure Entra ID (formerly Azure AD) authentication is misconfigured.
+
+**Solution:**
+
+1. For **App Registration** authentication:
+ - Verify the tenant ID, client ID, and client secret are correct.
+ - Ensure the app registration has been added as a user in the Azure SQL database.
+ - Check that the client secret hasn't expired.
+
+1. For **Managed Identity** authentication:
+ - Verify `managed_identity_enabled = true` is set in the Grafana server configuration.
+ - Ensure the managed identity has been added to the Azure SQL database.
+ - Confirm the Azure resource hosting Grafana has managed identity enabled.
+
+1. For **Current User** authentication:
+ - Ensure `user_identity_enabled = true` is set in the Grafana server configuration.
+ - Verify the app registration is configured to issue both Access Tokens and ID Tokens.
+ - Check that the required API permissions are configured (`user_impersonation` for Azure SQL).
+
+For detailed Azure authentication configuration, refer to [Configure the Microsoft SQL Server data source](ref:configure-mssql-data-source).
+
+## Query errors
+
+These errors occur when there are issues with query syntax or configuration.
+
+### Time column not found or invalid
+
+**Error message:** "Could not find time column" or time series visualization shows no data
+
+**Cause:** The query doesn't return a properly formatted `time` column for time series visualization.
+
+**Solution:**
+
+1. Ensure your query includes a column named `time` when using the **Time series** format.
+1. Use the `$__time()` macro to rename your date column: `$__time(your_date_column)`.
+1. Verify the time column is of a valid SQL date/time type (`datetime`, `datetime2`, `date`) or contains Unix epoch values.
+1. Ensure the result set is sorted by the time column using `ORDER BY`.
+
+### Macro expansion errors
+
+**Error message:** "Error parsing query" or macros appear unexpanded in the query
+
+**Cause:** Grafana macros are being used incorrectly.
+
+**Solution:**
+
+1. Verify macro syntax: use `$__timeFilter(column)` not `$_timeFilter(column)`.
+1. Macros don't work inside stored procedures—use explicit date parameters instead.
+1. Check that the column name passed to macros exists in your table.
+1. View the expanded query by clicking **Generated SQL** after running the query to debug macro expansion.
+
+### Timezone and time shift issues
+
+**Cause:** Time series data appears shifted or doesn't align with expected times.
+
+**Solution:**
+
+1. Store timestamps in UTC in your database to avoid timezone issues.
+1. Time macros (`$__time`, `$__timeFilter`, etc.) always expand to UTC values.
+1. If your timestamps are stored in local time, convert them to UTC in your query:
+
+ ```sql
+ SELECT
+ your_datetime_column AT TIME ZONE 'Your Local Timezone' AT TIME ZONE 'UTC' AS time,
+ value
+ FROM your_table
+ ```
+
+1. Don't pass timezone parameters to time macros—they're not supported.
+
+### Query returns too many rows
+
+**Error message:** "Result set too large" or browser becomes unresponsive
+
+**Cause:** The query returns more data than can be efficiently processed.
+
+**Solution:**
+
+1. Add time filters using `$__timeFilter(column)` to limit data to the dashboard time range.
+1. Use aggregations (`AVG`, `SUM`, `COUNT`) with `GROUP BY` instead of returning raw rows.
+1. Add a `TOP` clause to limit results: `SELECT TOP 1000 ...`.
+1. Use the `$__timeGroup()` macro to aggregate data into time intervals.
+
+### Stored procedure returns no data
+
+**Cause:** Stored procedure output isn't being captured correctly.
+
+**Solution:**
+
+1. Ensure the stored procedure uses `SELECT` statements, not just variable assignments.
+1. Remove `SET NOCOUNT ON` if present, or ensure it's followed by a `SELECT` statement.
+1. Verify the stored procedure parameters are being passed correctly.
+1. Test the stored procedure directly in SQL Server Management Studio with the same parameters.
+
+For more information on using stored procedures, refer to the [query editor documentation](ref:mssql-query-editor).
+
+## Performance issues
+
+These issues relate to slow queries or high resource usage.
+
+### Slow query execution
+
+**Cause:** Queries take a long time to execute.
+
+**Solution:**
+
+1. Reduce the dashboard time range to limit data volume.
+1. Add indexes to columns used in `WHERE` clauses and time filters.
+1. Use aggregations instead of returning individual rows.
+1. Increase the **Min time interval** setting to reduce the number of data points.
+1. Review the query execution plan in SQL Server Management Studio to identify bottlenecks.
+
+### Connection pool exhaustion
+
+**Error message:** "Too many connections" or "Connection pool exhausted"
+
+**Cause:** Too many concurrent connections to the database.
+
+**Solution:**
+
+1. Increase the **Max open** connection limit in the data source configuration.
+1. Enable **Auto max idle** to automatically manage idle connections.
+1. Reduce the number of panels querying the same data source simultaneously.
+1. Check for long-running queries that might be holding connections.
+
+## Other common issues
+
+The following issues don't produce specific error messages but are commonly encountered.
+
+### System databases appear in queries
+
+**Cause:** Queries accidentally access system databases.
+
+**Solution:**
+
+1. The query editor automatically excludes `tempdb`, `model`, `msdb`, and `master` from the database dropdown.
+1. Always specify the database in your data source configuration to restrict access.
+1. Ensure the database user only has permissions on the intended database.
+
+### Template variable queries fail
+
+**Cause:** Variable queries return unexpected results or errors.
+
+**Solution:**
+
+1. Verify the variable query syntax is valid SQL that returns a single column.
+1. Check that the data source connection is working.
+1. Ensure the user has permission to access the tables referenced in the variable query.
+1. Test the query in the query editor before using it as a variable query.
+
+### Data appears incorrect or misaligned
+
+**Cause:** Data formatting or type conversion issues.
+
+**Solution:**
+
+1. Use explicit column aliases to ensure consistent naming: `SELECT value AS metric`.
+1. Verify numeric columns are actually numeric types, not strings.
+1. Check for `NULL` values that might affect aggregations.
+1. Use the `FILL` option in `$__timeGroup()` macro to handle missing data points.
+
+## Get additional help
+
+If you continue to experience issues after following this troubleshooting guide:
+
+1. Check the [Grafana community forums](https://community.grafana.com/) for similar issues.
+1. Review the [Grafana GitHub issues](https://github.com/grafana/grafana/issues) for known bugs.
+1. Enable debug logging in Grafana to capture detailed error information.
+1. Check SQL Server logs for additional error details.
+1. Contact Grafana Support if you're an Enterprise or Cloud customer.
+
+When reporting issues, include:
+
+- Grafana version
+- SQL Server version
+- Error messages (redact sensitive information)
+- Steps to reproduce
+- Relevant query examples (redact sensitive data)
diff --git a/docs/sources/developer-resources/api-reference/http-api/dashboard_versions.md b/docs/sources/developer-resources/api-reference/http-api/dashboard_versions.md
index f4b46adad3f..370b1fed7e4 100644
--- a/docs/sources/developer-resources/api-reference/http-api/dashboard_versions.md
+++ b/docs/sources/developer-resources/api-reference/http-api/dashboard_versions.md
@@ -171,146 +171,3 @@ Status Codes:
- **200** - Ok
- **401** - Unauthorized
- **404** - Dashboard version not found
-
-## Restore dashboard by dashboard UID
-
-`POST /api/dashboards/uid/:uid/restore`
-
-Restores a dashboard to a given dashboard version using `uid`.
-
-**Example request for restoring a dashboard version**:
-
-```http
-POST /api/dashboards/uid/QA7wKklGz/restore
-Accept: application/json
-Content-Type: application/json
-Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
-
-{
- "version": 1
-}
-```
-
-JSON body schema:
-
-- **version** - The dashboard version to restore to
-
-**Example response**:
-
-```http
-HTTP/1.1 200 OK
-Content-Type: application/json; charset=UTF-8
-Content-Length: 67
-
-{
- "id": 70,
- "slug": "my-dashboard",
- "status": "success",
- "uid": "QA7wKklGz",
- "url": "/d/QA7wKklGz/my-dashboard",
- "version": 3
-}
-```
-
-JSON response body schema:
-
-- **slug** - the URL friendly slug of the dashboard's title
-- **status** - whether the restoration was successful or not
-- **version** - the new dashboard version, following the restoration
-
-Status codes:
-
-- **200** - OK
-- **400** - Bad request (specified version has the same content as the current dashboard)
-- **401** - Unauthorized
-- **404** - Not found (dashboard not found or dashboard version not found)
-- **500** - Internal server error (indicates issue retrieving dashboard tags from database)
-
-**Example error response**
-
-```http
-HTTP/1.1 404 Not Found
-Content-Type: application/json; charset=UTF-8
-Content-Length: 46
-
-{
- "message": "Dashboard version not found"
-}
-```
-
-JSON response body schema:
-
-- **message** - Message explaining the reason for the request failure.
-
-## Compare dashboard versions
-
-`POST /api/dashboards/calculate-diff`
-
-Compares two dashboard versions by calculating the JSON diff of them.
-
-**Example request**:
-
-```http
-POST /api/dashboards/calculate-diff HTTP/1.1
-Accept: text/html
-Content-Type: application/json
-Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
-
-{
- "base": {
- "dashboardId": 1,
- "version": 1
- },
- "new": {
- "dashboardId": 1,
- "version": 2
- },
- "diffType": "json"
-}
-```
-
-JSON body schema:
-
-- **base** - an object representing the base dashboard version
-- **new** - an object representing the new dashboard version
-- **diffType** - the type of diff to return. Can be "json" or "basic".
-
-**Example response (JSON diff)**:
-
-```http
-HTTP/1.1 200 OK
-Content-Type: text/html; charset=UTF-8
-
-
-
-
-```
-
-The response is a textual representation of the diff, with the dashboard values being in JSON, similar to the diffs seen on sites like GitHub or GitLab.
-
-Status Codes:
-
-- **200** - Ok
-- **400** - Bad request (invalid JSON sent)
-- **401** - Unauthorized
-- **404** - Not found
-
-**Example response (basic diff)**:
-
-```http
-HTTP/1.1 200 OK
-Content-Type: text/html; charset=UTF-8
-
-
-
-
-```
-
-The response here is a summary of the changes, derived from the diff between the two JSON objects.
-
-Status Codes:
-
-- **200** - OK
-- **400** - Bad request (invalid JSON sent)
-- **401** - Unauthorized
-- **404** - Not found
diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md
index 5500601829d..0fa0e059472 100644
--- a/docs/sources/introduction/grafana-enterprise.md
+++ b/docs/sources/introduction/grafana-enterprise.md
@@ -87,6 +87,7 @@ With a Grafana Enterprise license, you also get access to premium data sources,
- [CockroachDB](/grafana/plugins/grafana-cockroachdb-datasource)
- [Databricks](/grafana/plugins/grafana-databricks-datasource)
- [DataDog](/grafana/plugins/grafana-datadog-datasource)
+- [IBM Db2](/grafana/plugins/grafana-ibmdb2-datasource)
- [Drone](/grafana/plugins/grafana-drone-datasource)
- [DynamoDB](/grafana/plugins/grafana-dynamodb-datasource/)
- [Dynatrace](/grafana/plugins/grafana-dynatrace-datasource)
diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md
index 67c361b2bdc..d9c5f524bd3 100644
--- a/docs/sources/setup-grafana/configure-grafana/_index.md
+++ b/docs/sources/setup-grafana/configure-grafana/_index.md
@@ -2030,6 +2030,44 @@ For example: `disabled_labels=grafana_folder`
+### `[unified_alerting.state_history]`
+
+This section configures where Grafana Alerting writes alert state history. Refer to [Configure alert state history](/docs/grafana//alerting/set-up/configure-alert-state-history/) for end-to-end setup and examples.
+
+#### `enabled `
+
+Enables recording alert state history. Default is `false`.
+
+#### `backend `
+
+Select the backend used to store alert state history. Supported values: `loki`, `prometheus`, `multiple`.
+
+#### `loki_remote_url `
+
+The URL of the Loki server used when `backend = loki` (or when `backend = multiple` and Loki is a primary/secondary).
+
+#### `prometheus_target_datasource_uid `
+
+Target Prometheus data source UID used for writing alert state changes when `backend = prometheus` (or when `backend = multiple` and Prometheus is a secondary).
+
+#### `prometheus_metric_name `
+
+Optional. Metric name for the alert state metric. Default is `GRAFANA_ALERTS`.
+
+#### `prometheus_write_timeout `
+
+Optional. Timeout for writing alert state data to the target data source. Default is `10s`.
+
+#### `primary `
+
+Used only when `backend = multiple`. Selects the primary backend (for example `loki`).
+
+#### `secondaries `
+
+Used only when `backend = multiple`. Comma-separated list of secondary backends (for example `prometheus`).
+
+
+
### `[unified_alerting.state_history.annotations]`
This section controls retention of annotations automatically created while evaluating alert rules when alerting state history backend is configured to be annotations (see setting [unified_alerting.state_history].backend)
@@ -2836,9 +2874,11 @@ For more information about Grafana Enterprise, refer to [Grafana Enterprise](../
Keys of features to enable, separated by space.
-#### `FEATURE_TOGGLE_NAME = false`
+#### `FEATURE_NAME = `
-Some feature toggles for stable features are on by default. Use this setting to disable an on-by-default feature toggle with the name FEATURE_TOGGLE_NAME, for example, `exploreMixedDatasource = false`.
+Use a key-value pair to set feature flag values explicitly, overriding any default values. A few different types are supported, following the OpenFeature specification. See the defaults.ini file for more details.
+
+For example, to disable an on-by-default feature toggle named `exploreMixedDatasource`, specify `exploreMixedDatasource = 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 b7f55555e07..813efb29eba 100644
--- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
@@ -83,6 +83,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `reportingRetries` | Enables rendering retries for the reporting feature |
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
+| `dashboardNewLayouts` | Enables new dashboard layouts |
| `pdfTables` | Enables generating table data as PDF in reporting |
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage. Default is enabled. |
diff --git a/docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md b/docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md
index 2030cd1b2d8..a220ba21b53 100644
--- a/docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md
+++ b/docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md
@@ -78,9 +78,9 @@ For every dashboard and data source, you can access usage information.
### Dashboard insights
-To see dashboard usage information, click the dashboard insights icon in the header.
+To see dashboard usage information, click the dashboard insights icon in the sidebar.
-
+{{< figure src="/media/docs/grafana/dashboards/screenshot-dashboard-insights-v12.4.png" max-width="500px" alt="Dashboard insights icon" >}}
Dashboard insights show the following information:
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md
index 10196a40811..fd2deafa469 100644
--- a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md
+++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md
@@ -2,238 +2,423 @@
aliases:
- ../../../dashboards/build-dashboards/add-organize-panels/ # /docs/grafana/next/dashboards/build-dashboards/add-organize-panels/
- ../../../dashboards/build-dashboards/create-dashboard/ # /docs/grafana/next/dashboards/build-dashboards/create-dashboard/
+ - ../../../dashboards/build-dashboards/create-dynamic-dashboard/ # /docs/grafana/latest/dashboards/build-dashboards/create-dynamic-dashboard/
+ - ./create-dynamic-dashboard/ # /docs/grafana/latest/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/
keywords:
- panel
- dashboard
- create
+ - dynamic dashboard
labels:
products:
- cloud
- enterprise
- oss
-menuTitle: Create a dashboard
-title: Create a dashboard
+title: Create dashboards
description: Create and edit a dashboard
weight: 1
-refs:
- built-in-special-data-sources:
- - pattern: /docs/grafana/
- destination: /docs/grafana//datasources/#special-data-sources
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#special-data-sources
- visualization-specific-options:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/visualizations/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/
- configure-standard-options:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/configure-standard-options/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-standard-options/
- configure-value-mappings:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/configure-value-mappings/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-value-mappings/
- generative-ai-features:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
- configure-thresholds:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/configure-thresholds/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-thresholds/
- data-sources:
- - pattern: /docs/grafana/
- destination: /docs/grafana//datasources/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/
- add-a-data-source:
- - pattern: /docs/grafana/
- destination: /docs/grafana//datasources/#add-a-data-source
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana//datasources/#add-a-data-source
- about-users-and-permissions:
- - pattern: /docs/grafana/
- destination: /docs/grafana//administration/roles-and-permissions/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana//administration/roles-and-permissions/
- visualizations-options:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/visualizations/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana//panels-visualizations/visualizations/
- configure-repeating-panels:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/configure-panel-options/#configure-repeating-panels
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-panel-options/#configure-repeating-panels
- override-field-values:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/configure-overrides/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
- saved-queries:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/query-transform-data/#saved-queries
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
- save-query:
- - pattern: /docs/grafana/
- destination: /docs/grafana//visualizations/panels-visualizations/query-transform-data/#save-a-query
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
+image_maps:
+ - key: editpane-sidebar
+ src: /media/docs/grafana/dashboards/screenshot-edit-sidebar-v12.4.png
+ alt: An annotated image of the edit pane and sidebar
+ points:
+ - x_coord: 96
+ y_coord: 17
+ content: |
+ **Dashboard options**
+
+ Click the icon to open the edit pane. Edit mode only.
+ - x_coord: 96
+ y_coord: 25
+ content: |
+ **Feedback**
+
+ Submit feedback on the new editing experience. Edit mode only.
+ - x_coord: 96
+ y_coord: 33
+ content: |
+ **Export**
+
+ Click to display [export](https://grafana.com/docs/grafana//visualizations/dashboards/share-dashboards-panels/#export-dashboards) options.
+ - x_coord: 96
+ y_coord: 41
+ content: |
+ **Content outline**
+
+ Navigate a dashboard using the [Content outline](#navigate-using-the-content-outline).
+ - x_coord: 96
+ y_coord: 49
+ content: |
+ **Dashboard insights**
+
+ View [dashboard analytics](https://grafana.com/docs/grafana//visualizations/dashboards/assess-dashboard-usage/) including information about users, activity, and query counts.
---
-## Create a dashboard
+# Create dashboards
-Dashboards and panels allow you to show your data in visual form. Each panel needs at least one query to display a visualization.
+{{< admonition type="note">}}
+Dynamic dashboards is currently in public preview. Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
+
+For information on the generally available dashboard creation experience, refer to the [documentation for the latest self-managed version of Grafana](https://grafana.com/docs/grafana/latest/visualizations/dashboards/build-dashboards/create-dashboard/).
+{{< /admonition >}}
+
+Dashboards and panels allow you to show your data in visual form.
+Each panel needs at least one query to display a visualization.
**Before you begin:**
-- Ensure that you have the proper permissions. For more information about permissions, refer to [About users and permissions](ref:about-users-and-permissions).
-- Identify the dashboard to which you want to add the panel.
+- Ensure that you have the proper permissions. For more information about permissions, refer to [About users and permissions](https://grafana.com/docs/grafana//administration/roles-and-permissions/).
- Understand the query language of the target data source.
-- Ensure that data source for which you are writing a query has been added. For more information about adding a data source, refer to [Add a data source](ref:add-a-data-source) if you need instructions.
+
+## Create a dashboard
To create a dashboard, follow these steps:
-{{< shared id="create-dashboard" >}}
-
1. Click **Dashboards** in the main menu.
1. Click **New** and select **New Dashboard**.
-1. On the empty dashboard, click **+ Add visualization**.
-
- 
-
-{{< /shared >}}
-
+1. Click **+ Add visualization**.
1. In the dialog box that opens, do one of the following:
- Select one of your existing data sources.
- - Select one of the Grafana [built-in special data sources](ref:built-in-special-data-sources).
+ - Select one of the Grafana [built-in special data sources](https://grafana.com/docs/grafana//datasources/#special-data-sources).
- Click **Configure a new data source** to set up a new one (Admins only).
{{< figure class="float-right" src="/media/docs/grafana/dashboards/screenshot-data-source-selector-10.0.png" max-width="800px" alt="Select data source modal" >}}
The **Edit panel** view opens with your data source selected.
- You can change the panel data source later using the drop-down in the **Queries** tab of the panel editor if needed.
+ You can change the panel data source later using the drop-down in the **Query** tab of the panel editor if needed.
- For more information about data sources, refer to [Data sources](ref:data-sources) for specific guidelines.
+ For more information about data sources, refer to [Data sources](https://grafana.com/docs/grafana//datasources/) for specific guidelines.
1. To create a query, do one of the following:
- Write or construct a query in the query language of your data source.
- - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries).
+ - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](https://grafana.com/docs/grafana//visualizations/panels-visualizations/query-transform-data/#saved-queries).
-1. (Optional) To [save the query](ref:save-query) for reuse, open the **Saved queries** drop-down menu and click the **Save query** option.
-1. Click **Refresh** to query the data source.
-1. (Optional) To add subsequent queries, click **+ Add query** or **+ Add from saved queries**, and refresh the data source as many times as needed.
+1. (Optional) To [save the query](https://grafana.com/docs/grafana//visualizations/panels-visualizations/query-transform-data/#save-a-query) for reuse, open the **Saved queries** drop-down menu and click the **Save query** option.
{{< admonition type="note" >}}
- [Saved queries](ref:saved-queries) is currently in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Grafana Cloud only.
+ [Saved queries](https://grafana.com/docs/grafana//visualizations/panels-visualizations/query-transform-data/#saved-queries) is currently in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Grafana Cloud only.
{{< /admonition >}}
+1. Click **Refresh** to query the data source.
1. In the visualization list, select a visualization type.
- 
+ {{< figure src="/media/docs/grafana/dashboards/screenshot-select-visualization-v12.png" max-width="350px" alt="Visualization selector" >}}
Grafana displays a preview of your query results with the visualization applied.
- For more information about individual visualizations, refer to [Visualizations options](ref:visualizations-options).
+ For more information about configuring individual visualizations, refer to [Visualizations options](https://grafana.com/docs/grafana//visualizations/panels-visualizations/visualizations/).
-1. Under **Panel options**, enter a title and description for your panel or have Grafana create them using [generative AI features](ref:generative-ai-features).
+1. Under **Panel options**, enter a title and description for the panel or have Grafana create them using [generative AI features](https://grafana.com/docs/grafana//visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards).
1. Refer to the following documentation for ways you can adjust panel settings.
While not required, most visualizations need some adjustment before they properly display the information that you need.
- - [Configure value mappings](ref:configure-value-mappings)
- - [Visualization-specific options](ref:visualization-specific-options)
- - [Override field values](ref:override-field-values)
- - [Configure thresholds](ref:configure-thresholds)
- - [Configure standard options](ref:configure-standard-options)
+ - [Configure value mappings](https://grafana.com/docs/grafana//visualizations/panels-visualizations/configure-value-mappings/)
+ - [Visualization-specific options](https://grafana.com/docs/grafana//visualizations/panels-visualizations/visualizations/)
+ - [Override field values](https://grafana.com/docs/grafana//visualizations/panels-visualizations/configure-overrides/)
+ - [Configure thresholds](https://grafana.com/docs/grafana//visualizations/panels-visualizations/configure-thresholds/)
+ - [Configure standard options](https://grafana.com/docs/grafana//visualizations/panels-visualizations/configure-standard-options/)
-1. When you've finished editing your panel, click **Save dashboard**.
-
- Alternatively, click **Back to dashboard** if you want to see your changes applied to the dashboard first. Then click **Save dashboard** when you're ready.
-
-1. Enter a title and description for your dashboard or have Grafana create them using [generative AI features](ref:generative-ai-features).
+1. When you've finished editing the panel, click **Save**.
+1. Enter a title and description for the dashboard if you haven't already or have Grafana create them using [generative AI features](https://grafana.com/docs/grafana//visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards).
1. Select a folder, if applicable.
+1. Click **Save**
+1. Click **Back to dashboard**.
+1. (Optional) Continue building the dashboard by clicking one or more of the following options:
+ - **+ Add panel**: Set panel options in the edit pane or click **Configure** to complete panel setup.
+ - **+ Add variable**: Follow the steps to [add a variable to the dashboard](#add-variables).
+ - **Group panels**: Choose from **Group into row** or **Group into tab**. For more information on groupings, refer to [Panel groupings](#panel-groupings).
+ - **Dashboard options** icon: Open the edit pane to access [panel layout options](#panel-layouts).
+
+1. When you've finished making changes, click **Save**.
+1. (Optional) Enter a description of the changes you've made.
1. Click **Save**.
-1. To add more panels to the dashboard, click **Back to dashboard**.
- Then click **Add** in the dashboard header and select **Visualization** in the drop-down.
+1. Click **Exit edit**.
- 
+## Dashboard edit
- When you add additional panels to the dashboard, you're taken straight to the **Edit panel** view.
+Now that you've created a basic dashboard, you can augment it with more options.
+You can make several updates without leaving the dashboard by using the edit pane, which is explained in the next section.
-1. When you've saved all the changes you want to make to the dashboard, click **Exit edit**.
+### The edit pane and sidebar
- Now, when you want to make more changes to the saved dashboard, click **Edit** in the top-right corner.
+The _edit pane_ allows you to make changes without leaving the dashboard, by displaying options associated with the part of the dashboard that's in focus.
+The _sidebar_ is on the next to the edit pane, and it includes options that are useful to have available all the time.
-### Begin dashboard creation from data source configuration
+The following image shows the parts of the edit pane and the sidebar.
+Hover your cursor over the numbers to display descriptions of the sidebar options (descriptions also follow the image):
-You can start the process of creating a dashboard directly from a data source rather than from the **Dashboards** page.
+{{< image-map key="editpane-sidebar" >}}
-To begin building a dashboard directly from a data source, follow these steps:
+{{< admonition type="note" >}}
+The sidebar is displayed in both edit and view mode, but the **Dashboard options** and **Feedback** icons aren't available in view mode.
+{{< /admonition >}}
-1. Navigate to **Connections > Data sources**.
-1. On the row of the data source for which you want to build a dashboard, click **Build a dashboard**.
+You can dock, undock, and resize the edit pane.
+When the edit pane is closed, you can resize the sidebar so the icon names are visible.
- The empty dashboard page opens.
+{{< video-embed src="/media/docs/grafana/dashboards/screenrecord-edit-side-v12.4.mp4" >}}
+The available configuration options in the edit pane differ depending on the selected dashboard element:
+
+- Dashboards: High-level options are in the edit pane and further configuration options are in the **Settings** page.
+- Groupings (rows and tabs): All configuration options are available in the edit pane.
+- Panels: High-level options are in the edit pane and further configuration options are in the **Edit panel** view.
+
+### Navigate using the content outline
+
+The **Content outline** provides a tree-like structure that shows you all the parts of the dashboard and their relationships to each other, including panels, rows, tabs, and variables.
+The outline also lets you quickly navigate the dashboard and is available in both view and edit modes (note that variables are only included in edit mode).
+
+{{< figure src="/media/docs/grafana/dashboards/screenshot-content-outline-v12.4.png" max-width="750px" alt="Dashboard with outline open" >}}
+
+To navigate the dashboard using the outline, follow these steps:
+
+1. Navigate to the dashboard you want to view or update.
+1. In the right sidebar, click the **Content outline** icon to open it.
+1. Expand the outline to find the part of the dashboard you want to view or update.
+1. Click the tree item to navigate that part of the dashboard.
+
+### Edit a dashboard
+
+To edit a dashboard, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the part of the dashboard you want to update to open the edit pane, or click the **Dashboard options** icon to open it.
+
+ If the dashboard is large, open the **Content outline** and use it to navigate to the part of the dashboard you want to update.
+
+1. Update the dashboard as needed.
+1. When you've finished making changes, click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Back to dashboard**, if needed.
+1. Click **Exit edit**
+
+## Panel layouts
+
+Panel layouts control the size and arrangement of panels in the dashboard.
+There are two panel layout options:
+
+- **Custom**: You can position and size panels individually. This is the default selection for a new dashboard. **Show/hide rules** are not supported.
+- **Auto grid**: Panels resize and fit automatically to create a uniform grid. You can't make manual changes to this layout. **Show/hide rules** are supported.
+
+You can use both layouts in row or tab groupings.
+
+### Auto grid layout
+
+In the auto grid layout, panels are automatically sized and positioned as you add them.
+There are default parameters to constrain the layout, and you can update these to have more control over the display:
+
+- **Min column width**: Choose from **Standard**, **Narrow**, **Wide**, or **Custom**, for which you can enter the minimum width in pixels.
+- **Max columns**: Set a number up to 10.
+- **Row height**: Choose from **Standard**, **Short**, **Tall**, and **Custom**, for which you can enter the row height in pixels.
+- **Fill screen**: Toggle the switch on to have the panel fill the entire height of the screen. If the panel is in a row, the **Fill screen** toggle for the row must also be enabled (refer to [grouping configuration options](#grouping-configuration-options)).
+
+### Update panel layout
+
+To update the panel layout, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the dashboard or the grouping that contains the panel layout you want to update.
+1. Click the **Dashboard options** icon to open the edit pane, if needed.
+1. Under **Layout**, select **Custom** or **Auto grid**.
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**
+
+## Panel groupings
+
+To help create meaningful sections in your dashboard, you can group panels into rows or tabs.
+Rows and tabs let you break up big dashboards or make one dashboard out of several smaller ones.
+
+You can think of the dashboard as a series of nested containers: the dashboard is the largest container and it contains panels, rows, or tabs.
+Rows and tabs are the next largest containers, and they contain panels.
+
+You can also nest:
+
+- Rows in a row
+- Rows in a tab
+- Tabs in a row
+
+You can nest up to two levels deep, which means a dashboard can have a maximum of four configuration levels:
+
+- Dashboard
+- Grouping 1 - Row or tab
+- Grouping 2 - Row or tab
+- Panels
+
+You can only have one type of grouping at each level.
+Inside of those groupings however, you have to freedom to add different elements.
+Also, custom and auto grid panel layouts are supported for rows and tabs, so each grouping can have a different panel layout.
+
+
+
+The following sections describe:
+
+- [Grouping configuration options](#grouping-configuration-options)
+- [Grouping layouts](#grouping-layouts)
+- [How to group panels](#group-panels)
+- [How to ungroup panels](#ungroup-panels)
+
+### Grouping configuration options
+
+The following table describes the options you can set for a row or tab:
+
+
+
+| Option | Description |
+| ----------------| --------------------------------------------------------------------------- |
+| Title | Title of the row or tab. |
+| Fill screen | Toggle the switch on to make the row fill the screen. Rows only. |
+| Hide row header | Toggle the switch on to hide row headers in view mode. In edit mode, the row header is visible, but crossed out with the hidden icon next to it. Rows only. |
+| Layout | Select the layout. If the grouping contains another grouping, choose from **Rows** or **Tabs**. If the grouping contains panels, choose from **Custom** or **Auto grid**. For more information, refer to [Panel layouts](#panel-layouts) or [Grouping layouts](#grouping-layouts). |
+| Repeat options > [Repeat by variable](#configure-repeat-options) | Configure the dashboard to dynamically add panels, rows, or tabs based on the value of a variable. |
+| Show / hide rules > [Panel/Row/Tab visibility](#configure-showhide-rules) | Control whether or not panels, rows, or tabs are displayed based on variable values, a time range, or query results (panels only). |
+
+
+
+### Grouping layouts
+
+When you have panels grouped into rows or tabs, the **Layout** options available depend on which dashboard element is selected and the nesting level of that element.
+
+You can nest up to two levels deep, which means a dashboard can have a maximum of four configuration levels, with the following layout options:
+
+- **Dashboard**: Layout options allow you to choose between rows or tabs.
+- **Grouping 1 (outer)**: Layout options allow you to choose between rows or tabs.
+- **Grouping 2 (inner)**: Layout options allow you to choose between custom and auto grid (refer to [Panel layouts](#panel-layouts)).
+- **Panels**: No layout options
+
+You can switch between rows and tabs or update the panel layout by clicking the parent container and changing the layout selection.
+
+### Group panels
+
+To group panels, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Under a panel, click **Group panels**.
+
+ While grouping is typically used for multiple panels, you can start a grouping with just one panel.
+
+1. Select **Group into row** or **Group into tab**.
+
+ All the panels are moved into the grouping, and a dotted blue line surrounds the row or tab.
+ The edit pane opens, displaying the relevant options.
+
+1. Set the [grouping configuration options](#grouping-configuration-options) in the edit pane.
+1. (Optional) Add one or both of the following:
+ - A [nested grouping](#add-nested-groupings)
+ - Other [groupings at the same level](#add-more-groupings-at-the-same-level).
+
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**.
+
+#### Add nested groupings
+
+To add a second-level (or nested) grouping, follow these steps:
+
+1. In the existing grouping, under the panels, click **Group panels**.
+
+ {{< figure src="/media/docs/grafana/dashboards/screenshot-nest-group-v12.4.png" alt="Adding a nested grouping" max-width="500px" >}}
+
+1. Click **Group into row** or **Group into tab** (**Group into tab** is only available if the parent grouping is a row).
+
+ The new grouping is added inside the first grouping, and the panels are moved into the nested grouping.
+ The edit pane opens displaying the relevant options.
+
+1. Set the configuration options for the nested grouping.
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**.
+
+#### Add more groupings at the same level
+
+To add more first-level groupings, follow these steps:
+
+1. On the dashboard, outside the existing first-level grouping, click **New row** or **New tab** (only one option will be available).
+
+ {{< figure src="/media/docs/grafana/dashboards/screenshot-add-group-v12.4.png" alt="Adding a nested grouping" max-width="500px" >}}
+
+1. Set the configuration options for the new grouping.
+1. Click **+ Add panel** to begin adding panels.
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**.
+
+### Ungroup panels
+
+You can ungroup some or all of the dashboard groupings without losing your panels.
+Ungrouping behavior depends on whether you're working with first-level or nested groupings:
+
+| Grouping | Action and outcome |
+| ---------- | -------------------------------------------------------------------------------------------------- |
+| Rows | **Ungroup rows** ungroups all first-level rows in the dashboard and all of their nested groupings. |
+| Tabs | **Ungroup tabs** ungroups all first-level tabs in the dashboard and all of their nested groupings. |
+| Row > row | **Ungroup rows** ungroups the nested row. |
+| Row > tabs | **Ungroup tabs** ungroups all the nested tabs in that row. Tabs in other rows are not affected. |
+| Tab > rows | **Ungroup rows** ungroups all the nested rows in that tab. Rows in other tabs are not affected. |
+
+{{< figure src="/media/docs/grafana/dashboards/screenshot-ungrouping-v12.4.png" alt="Dashboard with ungrouping behavior annotated" max-width="750px" >}}
+
+{{< admonition type="caution" >}}
+If you delete a grouping, rather than ungrouping it, its panels are deleted as well.
+{{< /admonition >}}
+
+To remove groupings, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. (Optional) Click the **Content outline** icon to quickly navigate to the grouping you want to remove.
1. Do one of the following:
- - Click **+Add visualization** to configure all the elements of the new dashboard.
- - Select one of the suggested dashboards by clicking its **Use dashboard** button. This can be helpful when you're not sure how to most effectively visualize your data.
- The suggested dashboards are specific to your data source type (for example, Prometheus, Loki, or Elasticsearch). If there are more than three dashboard suggestions, you can click **View all** to see the rest of them.
-
- 
-
- {{< docs/public-preview product="Suggested dashboards" >}}
-
-1. Complete the rest of the dashboard configuration. For more detailed steps, refer to [Create a dashboard](#create-a-dashboard), beginning at step five.
-
-## Copy a dashboard
-
-To copy a dashboard, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Open the dashboard you want to copy.
-1. Click **Edit** in top-right corner.
-1. Click the **Save dashboard** drop-down and select **Save as copy**.
-1. (Optional) Specify the name, folder, description, and whether or not to copy the original dashboard tags for the copied dashboard.
-
- By default, the copied dashboard has the same name as the original dashboard with the word "Copy" appended and is in the same folder.
+ - Click **Ungroup rows** or **Ungroup tabs** at the bottom of the dashboard to ungroup all rows or tabs, including any nested groupings.
+ - Click in a grouping and click **Ungroup rows** or **Ungroup tabs** to ungroup only the tabs or rows nested in that grouping.
+1. If you've ungrouped panels that were previously in different panel layouts, you'll be prompted to select a common layout type for all the panels; click **Convert to Auto grid** or **Convert to Custom**.
1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**.
-## Configure repeating rows
+## Configure repeat options
-You can configure Grafana to dynamically add panels or rows to a dashboard based on the value of a variable. Variables dynamically change your queries across all rows in a dashboard. For more information about repeating panels, refer to [Configure repeating panels](ref:configure-repeating-panels).
+You can configure Grafana to dynamically add panels, rows, or tabs to a dashboard based on the value of a variable.
+Variables dynamically change your queries across all panels, rows, or tabs in a dashboard.
-To see an example of repeating rows, refer to [Dashboard with repeating rows](https://play.grafana.org/d/000000153/repeat-rows). The example shows that you can also repeat rows if you have variables set with `Multi-value` or `Include all values` selected.
+This only applies to queries that include a multi-value variable.
-**Before you begin:**
+To configure repeats, follow these steps:
-- Ensure that the query includes a multi-value variable.
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the panel, row, or tab you want to update to open the edit pane, or click the **Dashboard options** icon to open it.
-**To configure repeating rows:**
+ If the dashboard is large, open the **Content outline** and use it to navigate to the part of the dashboard you want to update.
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to work on.
-1. At the top of the dashboard, click **Add** and select **Row** in the drop-down.
+1. Expand the **Repeat options** section.
+1. Select the **Repeat by variable**.
+1. For panels in a custom layout, set the following options:
+ 1. Under **Repeat direction**, choose one of the following:
+ - **Horizontal** - Arrange panels side-by-side. Grafana adjusts the width of a repeated panel. You can’t mix other panels on a row with a repeated panel.
+ - **Vertical** - Arrange panels in a column. The width of repeated panels is the same as the original, repeated panel.
+ 1. If you selected **Horizontal**, select a value in the **Max per row** drop-down list to control the maximum number of panels that can be in a row.
- If the dashboard is empty, you can click the **+ Add row** button in the middle of the dashboard.
+1. (Optional) To provide context to dashboard users, add the variable name to the panel, row, or tab title.
+1. When you've finished setting the repeat option, click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**.
-1. Hover over the row title and click the cog icon.
-1. In the **Row Options** dialog box, add a title and select the variable for which you want to add repeating rows.
-1. Click **Update**.
+### Repeating rows and tabs and the Dashboard special data source
-To provide context to dashboard users, add the variable to the row title.
-
-### Repeating rows and the Dashboard special data source
-
-If a row includes panels using the special [Dashboard data source](ref:built-in-special-data-sources)—the data source that uses a result set from another panel in the same dashboard—then corresponding panels in repeated rows will reference the panel in the original row, not the ones in the repeated rows.
+If a row includes panels using the special [Dashboard data source](https://grafana.com/docs/grafana//datasources/#special-data-sources)—the data source that uses a result set from another panel in the same dashboard—then corresponding panels in repeated rows will reference the panel in the original row, not the ones in the repeated rows.
+The same behavior applies to tabs.
For example, in a dashboard:
@@ -242,28 +427,196 @@ For example, in a dashboard:
- Repeating row, `Row 2`, includes `Panel 2A` and `Panel 2B`
- `Panel 2B` references `Panel 1A`, not `Panel 2A`
-## Move a panel
+## Show/hide rules
-You can place a panel on a dashboard in any location.
+You can configure panels, rows, and tabs to be shown or hidden based on rules.
+For example, you can set a panel to be hidden if there's no data returned by a query or a tab to only be shown if a specific variable value is present.
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to work on.
-1. Click **Edit** in the top-right corner.
-1. Click the panel title and drag the panel to the new location.
-1. Click **Save dashboard**.
+There are three types of show/hide rules to choose from:
+
+- [Query result](#query-result-rule)
+- [Template variable](#template-variable-rule)
+- [Time range less than](#time-range-less-than-rule)
+
+For steps on how to create show/hide rules, refer to [Configure show/hide rules](#configure-showhide-rules).
+
+{{< admonition type="note" >}}
+You can only configure show/hide rules for panels in the **Auto grid** layout. Set the panel layout at the dashboard, row, or tab-level.
+{{< /admonition >}}
+
+### Query result rule
+
+Show or hide a panel based on whether or not the query returns any results.
+The rule provides **Has data** and **No data** options, so you can choose to show or hide the panel based on the presence or absence of data.
+
+For example, if you have a dashboard with several panels and only want panels that return data to appear, set the rule as follows:
+
+- Panel visibility > Show
+- Query result > Has data
+
+Alternatively, you might also want to troubleshoot a dashboard with several panels to see which ones contain broken queries that aren't returning any results.
+In this case, you'd set the rule as follows:
+
+- Panel visibility > Show
+- Query result > No data
+
+### Template variable rule
+
+Show or hide a panel, row, or tab dynamically based on the variable value.
+You can select any variable that's configured for the dashboard and choose from the following operators for maximum flexibility:
+
+- Equals
+- Not equals
+- Matches (regular expression values)
+- Not matches (regular expression values)
+
+You can [add more variables](#add-variables) if you need to without leaving the dashboard.
+
+### Time range less than rule
+
+Show or hide a panel, row, or tab if the dashboard time range is shorter than the selected time range.
+This ensures that as you change the time range of the dashboard, you only see data relevant to that time period.
+
+For example, a dashboard is tracking adoption of a feature over time has the following setup:
+
+- Dashboard time range is **Last 7 days**
+- One panel tracks weekly stats
+- One panel tracks daily stats
+
+For the panel tracking weekly stats, a rule is set up to hide it if the dashboard time range is less than 7 days.
+For the panel tracking daily stats, a rule is set up to hide it if the dashboard time range is less 24 hours.
+This configuration ensures that these time-based panels are only displayed when enough time has passed to make them relevant.
+
+For this rule type, you can select time ranges from **5 minutes** to **5 years**.
+
+### Configure show/hide rules
+
+To configure show/hide rules, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the panel, row, or tab you want to update to open the edit pane, or click the **Dashboard options** icon to open it.
+
+ If the dashboard is large, open the **Content outline** and use it to navigate to the part of the dashboard you want to update.
+
+1. Expand the **Show / hide rules** section.
+1. Select **Show** or **Hide** to set whether the panel, row, or tab is shown or hidden based on the rules outcome.
+1. Click **+ Add rule**.
+1. Select a rule type:
+ - **Query result**: Show or hide a panel based on query results. Choose from **Has data** and **No data**.
+ - **Template variable**: Show or hide the panel, row, or tab dynamically based on the variable value. Select a variable and operator and enter a value.
+ - **Time range less than**: Show or hide the panel, row, or tab if the dashboard time range is shorter than the selected time range. Select a time range from **5 minutes** to **5 years**.
+
+1. If you've configured more than rule, under **Match rules**, select one of the following:
+ - **Match all**: The panel, row, or tab is shown or hidden only if _all_ the rules are matched.
+ - **Match any**: The panel, row, or tab is shown or hidden if _any_ of the rules are matched.
+
+ This option is only displayed if you add multiple rules.
+
+1. When you've finished setting rules, click **Save**.
1. (Optional) Enter a description of the changes you've made.
1. Click **Save**.
-1. Click **Exit edit**.
+1. Click **Exit edit**
+
+Hidden panels, rows, or tabs aren't visible when the dashboard is in view mode.
+In edit mode, hidden dashboard elements are displayed with an icon or overlay indicating this.
+
+## Move a panel
+
+To move a panel, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Navigate to the panel you want to move.
+
+ If the dashboard is large, open the **Content outline** and use it to navigate to the panel.
+
+1. Click the panel title and drag the panel to another row or tab, or to a new position on the dashboard.
+
+ If the dashboard has groupings, you can only move the panel to another grouping.
+
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**
## Resize a panel
-You can size a dashboard panel to suits your needs.
+When your dashboard or grouping has a **Custom** layout, you can manually resize a panel.
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to work on.
-1. Click **Edit** in the top-right corner.
-1. To adjust the size of the panel, click and drag the lower-right corner of the panel.
-1. Click **Save dashboard**.
+To resize a panel, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Navigate to the panel you want to resize.
+
+ If the dashboard is large, open the **Content outline** and use it to navigate to the panel.
+
+1. Click and drag the lower-right corner of the panel to change the size of the panel.
+1. Click **Save**.
1. (Optional) Enter a description of the changes you've made.
1. Click **Save**.
-1. Click **Exit edit**.
+1. Click **Exit edit**
+
+## Add variables
+
+To add variables without leaving the dashboard, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click **+ Add variable** at the top of the dashboard.
+1. Choose a variable type from the list.
+1. Set the options for the variable.
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**
+
+### Add variables using the content outline
+
+You can also add variables without leaving the dashboard using the content outline.
+
+To access the variables creation flow this way, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the **Content outline** icon.
+1. Click **Variables** in the outline.
+1. Click **+ Add variable**.
+1. Complete the rest of the steps to [add a variable without leaving the dashboard](#add-variables).
+
+## Copy or duplicate dashboard elements
+
+You can copy and paste or duplicate panels, rows, and tabs.
+
+To copy or duplicate dashboard elements, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the panel, row, or tab you want to update to open the edit pane, or click the **Dashboard options** icon to open it.
+
+ If the dashboard is large, open the **Content outline** and use it to navigate to the part of the dashboard you want to update.
+
+1. In the top-corner of the edit pane, click the **Copy or Duplicate** icon and do one of the following:
+ - Click **Copy**.
+ - Click **Duplicate**. The duplicated element is added next to the original one. Proceed to step 6.
+
+1. If you selected **Copy**, navigate to the part of the dashboard where you want to add the copied element, and click **Paste panel**, **Paste row**, or **Paste tab**.
+1. Update the copied or duplicated element if needed.
+1. Click **Save**.
+1. (Optional) Enter a description of the changes you've made.
+1. Click **Save**.
+1. Click **Exit edit**
+
+## Copy a dashboard
+
+To make a copy of a dashboard, follow these steps:
+
+1. Navigate to the dashboard you want to update.
+1. Click **Edit**.
+1. Click the **Save** drop-down list and select **Save as copy**.
+1. (Optional) Specify the name, folder, description, and whether or not to copy the original dashboard tags for the copied dashboard.
+
+ By default, the copied dashboard has the same name as the original dashboard with the word "Copy" appended and is in the same folder.
+
+1. Click **Save**.
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/index.md
deleted file mode 100644
index 0167ff147e5..00000000000
--- a/docs/sources/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/index.md
+++ /dev/null
@@ -1,416 +0,0 @@
----
-labels:
- products:
- - cloud
- - oss
- stage:
- - experimental
-_build:
- list: false
-noindex: true
-title: Create a dynamic dashboard
-description: Create and edit a dynamic dashboard
-weight: 900
-refs:
- built-in-special-data-sources:
- - pattern: /docs/grafana/
- destination: /docs/grafana//datasources/#special-data-sources
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#special-data-sources
- visualization-specific-options:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/visualizations/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/
- configure-standard-options:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/configure-standard-options/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-standard-options/
- configure-value-mappings:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/configure-value-mappings/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-value-mappings/
- generative-ai-features:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
- configure-thresholds:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/configure-thresholds/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-thresholds/
- data-sources:
- - pattern: /docs/grafana/
- destination: /docs/grafana//datasources/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/
- add-a-data-source:
- - pattern: /docs/grafana/
- destination: /docs/grafana//datasources/#add-a-data-source
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana//datasources/#add-a-data-source
- about-users-and-permissions:
- - pattern: /docs/grafana/
- destination: /docs/grafana//administration/roles-and-permissions/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana//administration/roles-and-permissions/
- visualizations-options:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/visualizations/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana//panels-visualizations/visualizations/
- configure-repeating-panels:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/configure-panel-options/#configure-repeating-panels
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-panel-options/#configure-repeating-panels
- override-field-values:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/configure-overrides/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
-aliases:
- - ../../../dashboards/build-dashboards/create-dynamic-dashboard/ # /docs/grafana/next/dashboards/build-dashboards/create-dynamic-dashboard/
----
-
-# Create and edit dynamic dashboards
-
-{{< admonition type="caution" >}}
-
-Dynamic dashboards is an [experimental](https://grafana.com/docs/release-life-cycle/) feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog).
-
-**Do not enable this feature in production environments as it may result in the irreversible loss of data.**
-
-{{< /admonition >}}
-
-Dashboards and panels allow you to show your data in visual form. Each panel needs at least one query to display a visualization.
-
-## Before you begin
-
-- Ensure that you have the proper permissions. For more information about permissions, refer to [About users and permissions](ref:about-users-and-permissions).
-- Identify the dashboard to which you want to add the panel.
-- Understand the query language of the target data source.
-- Ensure that data source for which you are writing a query has been added. For more information about adding a data source, refer to [Add a data source](ref:add-a-data-source) if you need instructions.
-
-## Create a dashboard
-
-To create a dashboard, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Click **New** and select **New Dashboard**.
-1. In the edit pane, enter the dashboard title and description.
-
- {{< figure src="/media/docs/grafana/dashboards/screenshot-new-dashboard-v12.png" max-width="750px" alt="New dashboard" >}}
-
-1. Under **Panel layout**, choose one of the following options:
- - **Custom** - Position and size panels manually. The default selection.
- - **Auto grid** - Panels are automatically resized to create a uniform grid based on the column and row settings.
-
-1. Click **+ Add visualization**.
-1. In the dialog box that opens, do one of the following:
- - Select one of your existing data sources.
- - Select one of the Grafana [built-in special data sources](ref:built-in-special-data-sources).
- - Click **Configure a new data source** to set up a new one (Admins only).
-
- {{< figure class="float-right" src="/media/docs/grafana/dashboards/screenshot-data-source-selector-10.0.png" max-width="800px" alt="Select data source modal" >}}
-
- The **Edit panel** view opens with your data source selected.
- You can change the panel data source later using the drop-down in the **Query** tab of the panel editor if needed.
-
- For more information about data sources, refer to [Data sources](ref:data-sources) for specific guidelines.
-
-1. Write or construct a query in the query language of your data source.
-1. Click **Refresh** to query the data source.
-1. In the visualization list, select a visualization type.
-
- {{< figure src="/media/docs/grafana/dashboards/screenshot-select-visualization-v12.png" max-width="350px" alt="Visualization selector" >}}
-
- Grafana displays a preview of your query results with the visualization applied.
-
- For more information about configuring individual visualizations, refer to [Visualizations options](ref:visualizations-options).
-
-1. Under **Panel options**, enter a title and description for your panel or have Grafana create them using [generative AI features](ref:generative-ai-features).
-1. Refer to the following documentation for ways you can adjust panel settings.
-
- While not required, most visualizations need some adjustment before they properly display the information that you need.
- - [Configure value mappings](ref:configure-value-mappings)
- - [Visualization-specific options](ref:visualization-specific-options)
- - [Override field values](ref:override-field-values)
- - [Configure thresholds](ref:configure-thresholds)
- - [Configure standard options](ref:configure-standard-options)
-
-1. When you've finished editing your panel, click **Save**.
-
- Alternatively, click **Back to dashboard** if you want to see your changes applied to the dashboard first. Then click **Save** when you're ready.
-
-1. Enter a title and description for your dashboard if you haven't already or have Grafana create them using [generative AI features](ref:generative-ai-features).
-1. Select a folder, if applicable.
-1. (Optional) Enter a description of the changes you've made.
-1. Click **Save**.
-1. To add more panels to the dashboard, click **Back to dashboard** and at the bottom-left corner of the dashboard, click **+ Add panel**.
-
- {{< figure src="/media/docs/grafana/dashboards/screenshot-add-panel-v12.png" max-width="500px" alt="Add panel button" >}}
-
-1. (Optional) In the edit pane, enter a title and description for the panel and set the panel transparency and repeat options, if applicable.
-1. Click **Configure** in either the edit pane or on the panel to the configuration process.
-1. When you've saved all the changes you want to make to the dashboard, click **Back to dashboard**.
-1. Toggle off the edit mode switch.
-
-{{< admonition type="caution" >}}
-
-Dynamic dashboards is an [experimental](https://grafana.com/docs/release-life-cycle/) feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog).
-
-**Do not enable this feature in production environments as it may result in the irreversible loss of data.**
-
-{{< /admonition >}}
-
-## Group panels
-
-To help create meaningful sections in your dashboard, you can group panels into rows or tabs.
-Rows and tabs let you break up big dashboards or make one dashboard out of several smaller ones.
-You can nest tabs and rows within each other or themselves.
-Also, tabs are included in the dashboard URL.
-
-The following sections describe the configuration options for adding tabs and rows.
-While grouping is meant for multiple panels, you can start a grouping with just one panel.
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-1. At the bottom-left corner of the dashboard, click **Group panels**.
-1. Select **Group into row** or **Group into tab**.
-
- A dotted line surrounds the panels and the **Row** or **Tab** edit pane is displayed on the right side of the dashboard.
-
-1. Set the [grouping configuration options](#grouping-configuration-options).
-1. When you're finished, click **Save** at the top-right corner of the dashboard.
-1. (Optional) Enter a description of the changes you've made.
-1. Click **Save**.
-
-### Grouping configuration options
-
-The following table describes the options you can set for a row.
-
-
-
-| Option | Description |
-| ------ | ----------- |
-| Title | Title of the row or tab. |
-| Fill screen | Toggle the switch on to make the row fill the screen. Only applies to rows. |
-| Hide row header | Toggle the switch on to hide the header. In edit mode, the row header is visible, but crossed out with the hidden icon next to it. Only applies to rows. |
-| Group layout | Select the grouping option, between **Rows** and **Tabs**. Only available when there's a nested grouping and applies to the nested grouping. |
-| Panel layout | Select whether panels are sized and positioned manually, **Custom**, or automatically, **Auto grid**. Only available when a grouping contains panels. |
-| Repeat options > [Repeat by variable](#configure-repeat-options) | Configure the dashboard to dynamically add rows or tabs based on the value of a variable. |
-| Show / hide rules > [Row/Tab visibility](#configure-showhide-rules) | Control whether or not rows or tabs are displayed based on variables or a time range. |
-
-
-
-## Configure repeat options
-
-
-
-You can configure Grafana to dynamically add panels, rows, or tabs to a dashboard based on the value of that variable.
-Variables dynamically change your queries across all rows in a dashboard.
-
-This only applies to queries that include a multi-value variable.
-
-
-
-To configure repeats, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-
- The **Dashboard** edit pane opens on the right side of the dashboard.
-
-1. Click in the panel, row, or tab you want to work with to bring it into focus and display the associated options in the edit pane.
-1. Expand the **Repeat options** section.
-1. Select the **Repeat by variable**.
-1. For panels only, set the following options:
- - Under **Repeat direction**, choose one of the following:
- - **Horizontal** - Arrange panels side-by-side. Grafana adjusts the width of a repeated panel. You can’t mix other panels on a row with a repeated panel.
- - **Vertical** - Arrange panels in a column. The width of repeated panels is the same as the original, repeated panel.
-
- - If you selected **Horizontal**, select a value in the **Max per row** drop-down list to control the maximum number of panels that can be in a row.
-
-1. (Optional) To provide context to dashboard users, add the variable name to the panel, row, or tab title.
-1. When you've finished setting the repeat option, click **Save**.
-1. (Optional) Enter a description of the changes you've made.
-1. Click **Save**.
-1. Toggle off the edit mode switch.
-
-### Repeating rows and tabs and the Dashboard special data source
-
-
-
-If a row includes panels using the special [Dashboard data source](ref:built-in-special-data-sources)—the data source that uses a result set from another panel in the same dashboard—then corresponding panels in repeated rows will reference the panel in the original row, not the ones in the repeated rows.
-The same behavior applies to tabs.
-
-For example, in a dashboard:
-
-- `Row 1` includes `Panel 1A` and `Panel 1B`
-- `Panel 1B` uses the results from `Panel 1A` by way of the `-- Dashboard --` data source
-- Repeating row, `Row 2`, includes `Panel 2A` and `Panel 2B`
-- `Panel 2B` references `Panel 1A`, not `Panel 2A`
-
-## Configure show/hide rules
-
-You can configure panels, rows, and tabs to be shown or hidden based on rules.
-For example, you might want to set a panel to be hidden if there's no data returned by a query or a tab to only be shown based on a variable being present.
-
-{{< admonition type="note" >}}
-You can only configure show/hide rules for panels when the dashboard is using the **Auto grid** panel layout.
-{{< /admonition >}}
-
-To configure show/hide rules, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-
- The **Dashboard** edit pane opens on the right side of the dashboard.
-
-1. Click in the panel, row, or tab you want to work with to bring it into focus and display the associated options in the edit pane.
-1. Expand the **Show / hide rules** section.
-1. Select **Show** or **Hide** to set whether the panel, row, or tab is shown or hidden based on the rules outcome.
-1. Click **+ Add rule**.
-1. Select a rule type:
- - **Query result** - Show or hide a panel based on query results. Choose from **Has data** and **No data**. For panels only.
- - **Template variable** - Show or hide the panel, row, or tab dynamically based on the variable value. Select a variable and operator and enter a value.
- - **Time range less than** - Show or hide the panel, row, or tab if the dashboard time range is shorter than the selected time frame. Select or enter a time range.
-
-1. Configure the rule.
-1. Under **Match rules**, select one of the following:
- - **Match all** - The panel, row, or tab is shown or hidden only if _all_ the rules are matched.
- - **Match any** - The panel, row, or tab is shown or hidden if _any_ of the rules are matched.
-
- This option is only displayed if you add multiple rules.
-
-1. When you've finished setting rules, click **Save**.
-1. (Optional) Enter a description of the changes you've made.
-1. Click **Save**.
-1. Toggle off the edit mode switch.
-
-{{< admonition type="caution" >}}
-
-Dynamic dashboards is an [experimental](https://grafana.com/docs/release-life-cycle/) feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog).
-
-**Do not enable this feature in production environments as it may result in the irreversible loss of data.**
-
-{{< /admonition >}}
-
-## Edit dashboards
-
-When the dashboard is in edit mode, the edit pane that opens displays options associated with the part of the dashboard that it's in focus.
-For example, if you click in the area of a panel, row, or tab, that area comes into focus and the edit pane shows the options for that area:
-
-{{< figure src="/media/docs/grafana/dashboards/screenshot-edit-pane-focus-v12.png" max-width="750px" alt="Dashboard with a panel in focus" >}}
-
-- For rows and tabs, all of the available options are in the edit pane.
-- For panels, high-level options are in the edit pane and further configuration options are in the **Edit panel** view.
-- For dashboards, high-level options are in the edit pane and further configuration options are in the **Settings** page.
-
-To edit dashboards, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-
- The **Dashboard** edit pane opens on the right side of the dashboard.
-
-1. Click in the area you want to work with to bring it into focus and display the associated options in the edit pane.
-1. Do one of the following:
- - For rows or tabs, make the required changes using the edit pane.
- - For panels, update the panel title, description, repeat options or show/hide rules in the edit pane. For more changes, click **Configure** and continue in **Edit panel** view.
- - For dashboards, update the dashboard title, description, grouping or panel layout. For more changes, click the settings (gear) icon in the top-right corner.
-
-1. When you've finished making changes, click **Save**.
-1. (Optional) Enter a description of the changes you've made.
-1. Click **Save**.
-1. Toggle off the edit mode switch.
-
-### Undo and redo
-
-When a dashboard is in edit mode, you can undo and redo changes you've made using the buttons on the toolbar:
-
-{{< figure src="/media/docs/grafana/dashboards/screenshot-undo-redo-icons-v12.0.png" max-width="500px" alt="Undo and redo buttons" >}}
-
-When you've made a change and hover the cursor over the buttons, the tooltip displays the change you're about to undo or redo.
-Also, you can continue undoing or redoing as many changes as you need:
-
-{{< video-embed src="/media/docs/grafana/dashboards/screen-record-undo-redo-v12.0.mp4" >}}
-
-The undo and redo buttons are only available at the dashboard level and only apply to changes made there, such as dashboard layout and grouping and high-level dashboard or panel updates.
-They aren't visible and don't apply when you're configuring a panel or making changes in the dashboard settings.
-
-{{< admonition type="note" >}}
-Not all dashboard edit actions can be undone or redone yet.
-{{< /admonition >}}
-
-## Move or resize a panel
-
-
-
-When you're dashboard has a **Custom** layout, you can resize or move a panel to any location on the dashboard.
-
-To move or resize, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-1. Do one of the following:
- - Click the panel title and drag the panel to the new location.
- - Click and drag the lower-right corner of the panel to change the size of the panel.
-
-1. Click **Save**.
-1. (Optional) Enter a description of the changes you've made.
-1. Click **Save**.
-1. Toggle off the edit mode switch.
-
-## Navigate using the dashboard outline
-
-The dashboard **Outline** provides a tree-like structure that shows you all of the parts of your dashboard and their relationships to each other including panels, rows, tabs, and variables.
-The outline also lets you quickly navigate the dashboard so that you don't have to spend time finding a particular element to work with it.
-By default, the outline is collapsed except for the part that's currently in focus.
-
-{{< figure src="/media/docs/grafana/dashboards/screenshot-dashboard-outline-v12.png" max-width="750px" alt="Dashboard with outline open showing panel in focus" >}}
-
-To navigate the dashboard using the outline, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-
- The **Dashboard** edit pane opens on the right side of the dashboard.
-
-1. In the edit pane, expand the **Outline** section.
-1. Expand the outline to find the dashboard part to which you want to navigate.
-1. Click the tree item to navigate that part of the dashboard.
-
-## Copy a dashboard
-
-To make a copy of a dashboard, follow these steps:
-
-1. Click **Dashboards** in the main menu.
-1. Navigate to the dashboard you want to update.
-1. Toggle on the edit mode switch.
-1. Click the **Save** drop-down and select **Save as copy**.
-1. (Optional) Specify the name, folder, description, and whether or not to copy the original dashboard tags for the copied dashboard.
-
- By default, the copied dashboard has the same name as the original dashboard with the word "Copy" appended and is in the same folder.
-
-1. Click **Save**.
-
-{{< admonition type="caution" >}}
-
-Dynamic dashboards is an [experimental](https://grafana.com/docs/release-life-cycle/) feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog).
-
-**Do not enable this feature in production environments as it may result in the irreversible loss of data.**
-
-{{< /admonition >}}
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/create-template-dashboards/_index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-template-dashboards/_index.md
index e737b240189..d741b3b53da 100644
--- a/docs/sources/visualizations/dashboards/build-dashboards/create-template-dashboards/_index.md
+++ b/docs/sources/visualizations/dashboards/build-dashboards/create-template-dashboards/_index.md
@@ -3,20 +3,25 @@ keywords:
- grafana
- dashboard
- template
+ - suggestions
labels:
products:
- cloud
- enterprise
- oss
-menuTitle: Create template dashboards
-title: Create dashboards from templates
-description: Learn how to create dashboards from templates
+menuTitle: Create template and suggested dashboards
+title: Create dashboards from templates and suggestions
+description: Learn how to create dashboards from templates and suggestions
weight: 3
---
-{{< docs/public-preview product="Dashboard templates" >}}
+# Create dashboards from templates and suggestions
-# Create dashboards from templates
+Grafana provides alternative ways to start building a dashboard.
+
+## Create dashboards from templates
+
+{{< docs/public-preview product="Dashboard templates" >}}
Grafana provides a variety of pre-built dashboard templates that you can use to quickly set up visualizations for your data. These dashboards use sample data, which you can replace with your own data, making it easier to get started with monitoring and analysis.
@@ -48,3 +53,23 @@ To create a dashboard from a template, follow these steps:
{{< figure src="/media/docs/grafana/dashboards/screenshot-remove-banner-v12.3.png" max-width="750px" alt="Removing the sample data banner panel" >}}
1. Click **Save dashboard**.
+
+## Create dashboards from suggestions
+
+{{< docs/public-preview product="Suggested dashboards" >}}
+
+You can start the process of creating a dashboard directly from a data source rather than from the **Dashboards** page, which gives you access to suggestions based on the data source.
+
+To begin building a dashboard directly from a data source, follow these steps:
+
+1. Navigate to **Connections > Data sources**.
+1. On the row of the data source for which you want to build a dashboard, click **Build a dashboard**.
+
+ The empty dashboard page opens.
+
+1. Select one of the suggested dashboards by clicking its **Use dashboard** button. This can be helpful when you're not sure how to most effectively visualize your data.
+ The suggested dashboards are specific to your data source type (for example, Prometheus, Loki, or Elasticsearch). If there are more than three dashboard suggestions, you can click **View all** to see the rest of them.
+
+ 
+
+1. Complete the rest of the dashboard configuration. For more detailed steps, refer to [Create a dashboard](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/create-dashboard/), beginning at step five.
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md b/docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md
index 3e94ec5aa22..104f3e69b07 100644
--- a/docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md
+++ b/docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md
@@ -85,7 +85,8 @@ Once you've added a dashboard link, it appears in the upper right corner of your
Add links to other dashboards at the top of your current dashboard.
1. In the dashboard you want to link, click **Edit**.
-1. Click **Settings**.
+1. In the sidebar, click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
1. Go to the **Links** tab and then click **Add dashboard link**.
The default link type is **Dashboards**.
@@ -109,7 +110,8 @@ Add links to other dashboards at the top of your current dashboard.
Add a link to a URL at the top of your current dashboard. You can link to any available URL, including dashboards, panels, or external sites. You can even control the time range to ensure the user is zoomed in on the right data in Grafana.
1. In the dashboard you want to link, click **Edit**.
-1. Click **Settings**.
+1. In the sidebar, click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
1. Go to the **Links** tab and then click **Add dashboard link**.
1. In the **Type** drop-down, select **Link**.
1. In the **URL** field, enter the URL to which you want to link.
@@ -132,7 +134,8 @@ Add a link to a URL at the top of your current dashboard. You can link to any av
To edit, duplicate, or delete dashboard link, follow these steps:
1. In the dashboard you want to link, click **Edit**.
-1. Click **Settings**.
+1. In the sidebar, click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
1. Go to the **Links** tab.
1. Do one of the following:
- **Edit** - Click the name of the link and update the link settings.
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md b/docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md
index 712cb4c4205..2f08fbed199 100644
--- a/docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md
+++ b/docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md
@@ -14,7 +14,7 @@ labels:
- cloud
- enterprise
- oss
-menutitle: Manage version history
+menuTitle: Manage version history
title: Manage dashboard version history
description: View and compare previous versions of your dashboard
weight: 400
@@ -32,8 +32,9 @@ The dashboard version history feature lets you compare and restore to previously
To compare two dashboard versions, follow these steps:
-1. Click **Edit** in the top-right corner of the dashboard.
-1. Click **Settings**.
+1. Click **Edit**.
+1. In the sidebar, click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
1. Go to the **Versions** tab.
1. Select the two dashboard versions that you want to compare.
1. Click **Compare versions** to view the diff between the two versions.
@@ -49,8 +50,9 @@ When you're comparing versions, if one of the versions you've selected is the la
To restore to a previously saved dashboard version, follow these steps:
-1. Click **Edit** in the top-right corner of the dashboard.
-1. Click **Settings**.
+1. Click **Edit**.
+1. Click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
1. Go to the **Versions** tab.
1. Click the **Restore** button next to the version.
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md b/docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md
index dbc49c40bde..e9f1f408c0d 100644
--- a/docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md
+++ b/docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md
@@ -50,8 +50,9 @@ The dashboard settings page allows you to:
To access the dashboard setting page:
-1. Click **Edit** in the top-right corner of the dashboard.
-1. Click **Settings**.
+1. Click **Edit**.
+1. In the sidebar, click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
## Modify dashboard time settings
diff --git a/docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md b/docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md
index 7720a4c946a..b09283bc190 100644
--- a/docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md
+++ b/docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md
@@ -3,45 +3,75 @@ aliases:
- ../../../reference/dashboard/ # /docs/grafana/next/reference/dashboard/
- ../../../dashboards/json-model/ # /docs/grafana/next/dashboards/json-model/
- ../../../dashboards/build-dashboards/view-dashboard-json-model/ # /docs/grafana/next/dashboards/build-dashboards/view-dashboard-json-model/
+ - ../../../as-code/observability-as-code/schema-v2/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/
+ - ../../../as-code/observability-as-code/schema-v2/annotations-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/annotations-schema/
+ - ../../../as-code/observability-as-code/schema-v2/panel-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/panel-schema/
+ - ../../../as-code/observability-as-code/schema-v2/librarypanel-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/librarypanel-schema/
+ - ../../../as-code/observability-as-code/schema-v2/layout-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/layout-schema/
+ - ../../../as-code/observability-as-code/schema-v2/links-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/links-schema/
+ - ../../../as-code/observability-as-code/schema-v2/timesettings-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/timesettings-schema/
+ - ../../../as-code/observability-as-code/schema-v2/variables-schema/ # /docs/grafana/latest/as-code/observability-as-code/schema-v2/variables-schema/
+ - ../../../observability-as-code/schema-v2/ # /docs/grafana/latest/observability-as-code/schema-v2/
+ - ../../../../next/observability-as-code/schema-v2/annotations-schema/ # /docs/grafana/next/observability-as-code/schema-v2/annotations-schema/
+ - ../../../../next/observability-as-code/schema-v2/panel-schema/ # /docs/grafana/next/observability-as-code/schema-v2/panel-schema/
+ - ../../../../next/observability-as-code/schema-v2/librarypanel-schema/ # /docs/grafana/next/observability-as-code/schema-v2/librarypanel-schema/
+ - ../../../../next/observability-as-code/schema-v2/layout-schema/ # /docs/grafana/next/observability-as-code/schema-v2/layout-schema/
+ - ../../../../next/observability-as-code/schema-v2/links-schema/ # /docs/grafana/next/observability-as-code/schema-v2/links-schema/
+ - ../../../../next/observability-as-code/schema-v2/timesettings-schema/ # /docs/grafana/next/observability-as-code/schema-v2/timesettings-schema/
+ - ../../../../next/observability-as-code/schema-v2/variables-schema/ # /docs/grafana/next/observability-as-code/schema-v2/variables-schema/
keywords:
- grafana
- dashboard
- documentation
- json
- model
+ - schema v2
+ - v1 resource
+ - v2 resource
+ - classic
labels:
products:
- cloud
- enterprise
- oss
title: JSON model
-description: View your Grafana dashboard JSON object
+description: View and update your Grafana dashboard JSON object
weight: 700
-refs:
- annotations:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/build-dashboards/annotate-visualizations/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/annotate-visualizations/
---
# Dashboard JSON model
-A dashboard in Grafana is represented by a JSON object, which stores metadata of its dashboard. Dashboard metadata includes dashboard properties, metadata from panels, template variables, panel queries, etc.
+Grafana dashboards are represented as JSON objects that store metadata, panels, variables, and settings.
-To view the JSON of a dashboard:
+## Different dashboard schema models
-1. Click **Edit** in the top-right corner of the dashboard.
-1. Click **Settings**.
+There are currently three dashboard JSON schema models:
+
+- [Classic](#classic-model) - A non-Kubernetes resource used before the adoption of the Kubernetes API by Grafana in v12.2.0. It's been widely used for exporting, importing, and sharing dashboards in the Grafana dashboards collection at [grafana.com/dashboards](https://grafana.com/grafana/dashboards/).
+- [V1 Resource](#v1-resource-model) - The Classic dashboard schema formatted as a Kubernetes-style resource. Its `spec` property contains the Classic model of the schema. This is the default format for API communication after Grafana v12.2.0, which enabled the Kubernetes Platform API as default backend for Grafana dashboards. Dashboards created using the Classic model can be exported using either the Classic or the V1 Resource format.
+- [V2 Resource](#v2-resource-model) - The latest format, supporting new features such as advanced layouts and conditional rendering. It models all dashboard elements as Kubernetes kinds, following Kubernetes conventions for declaring dashboard components. This format is future-proof and represents the evolving standard for dashboards.
+
+{{< admonition type="note" >}}
+[Observability as Code](https://grafana.com/docs/grafana/latest/as-code/observability-as-code/) works with all versions of the JSON model, and it's fully compatible with version 2.
+{{< /admonition >}}
+
+## Access and update the JSON model (#view-json)
+
+To access the JSON representation of a dashboard:
+
+1. Click **Edit**.
+1. In the sidebar, click the **Dashboard options** icon.
+1. In the edit pane, click **Settings**.
1. Go to the **JSON Model** tab.
1. When you've finished viewing the JSON, click **Back to dashboard** and **Exit edit**.
-## JSON fields
+## Classic model
-When a user creates a new dashboard, a new dashboard JSON object is initialized with the following fields:
+When you create a new dashboard in self-managed Grafana, a new dashboard JSON object was initialized with the following fields:
{{< admonition type="note" >}}
-In the following JSON, id is shown as null which is the default value assigned to it until a dashboard is saved. Once a dashboard is saved, an integer value is assigned to the `id` field.
+In the following JSON, id is shown as null which is the default value assigned to it until a dashboard is saved.
+After a dashboard is saved, an integer value is assigned to the `id` field.
{{< /admonition >}}
```json
@@ -76,26 +106,30 @@ In the following JSON, id is shown as null which is the default value assigned t
Each field in the dashboard JSON is explained below with its usage:
-| Name | Usage |
-| ----------------- | ----------------------------------------------------------------------------------------------------------------- |
-| **id** | unique numeric identifier for the dashboard. (generated by the db) |
-| **uid** | unique dashboard identifier that can be generated by anyone. string (8-40) |
-| **title** | current title of dashboard |
-| **tags** | tags associated with dashboard, an array of strings |
-| **style** | theme of dashboard, i.e. `dark` or `light` |
-| **timezone** | timezone of dashboard, i.e. `utc` or `browser` |
-| **editable** | whether a dashboard is editable or not |
-| **graphTooltip** | 0 for no shared crosshair or tooltip (default), 1 for shared crosshair, 2 for shared crosshair AND shared tooltip |
-| **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc |
-| **timepicker** | timepicker metadata, see [timepicker section](#timepicker) for details |
-| **templating** | templating metadata, see [templating section](#templating) for details |
-| **annotations** | annotations metadata, see [annotations](ref:annotations) for how to add them |
-| **refresh** | auto-refresh interval |
-| **schemaVersion** | version of the JSON schema (integer), incremented each time a Grafana update brings changes to said schema |
-| **version** | version of the dashboard (integer), incremented each time the dashboard is updated |
-| **panels** | panels array, see below for detail. |
+
-## Panels
+| Name | Usage |
+| ----------------- | ------------------------------------------------------------------------------------------ |
+| **id** | unique numeric identifier for the dashboard. (generated by the db) |
+| **uid** | unique dashboard identifier that can be generated by anyone. string (8-40) |
+| **title** | current title of dashboard |
+| **tags** | tags associated with dashboard, an array of strings |
+| **style** | theme of dashboard, i.e. `dark` or `light` |
+| **timezone** | timezone of dashboard, i.e. `utc` or `browser` |
+| **editable** | whether a dashboard is editable or not |
+| **graphTooltip** | 0 for no shared crosshair or tooltip (default), 1 for shared crosshair, 2 for shared crosshair AND shared tooltip |
+| **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc |
+| **timepicker** | timepicker metadata, see [timepicker section](#timepicker) for details |
+| **templating** | templating metadata, see [templating section](#templating) for details |
+| **annotations** | annotations metadata, see [annotations](https://grafana.com/docs/grafana//dashboards/build-dashboards/annotate-visualizations/) for how to add them |
+| **refresh** | auto-refresh interval|
+| **schemaVersion** | version of the JSON schema (integer), incremented each time a Grafana update brings changes to said schema |
+| **version** | version of the dashboard (integer), incremented each time the dashboard is updated |
+| **panels** | panels array, see below for detail. |
+
+
+
+### Panels
Panels are the building blocks of a dashboard. It consists of data source queries, type of graphs, aliases, etc. Panel JSON consists of an array of JSON objects, each representing a different panel. Most of the fields are common for all panels but some fields depend on the panel type. Following is an example of panel JSON of a text panel.
@@ -168,18 +202,22 @@ The grid has a negative gravity that moves panels up if there is empty space abo
Usage of the fields is explained below:
-| Name | Usage |
-| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
-| **collapse** | whether timepicker is collapsed or not |
-| **enable** | whether timepicker is enabled or not |
-| **notice** | |
-| **now** | |
-| **hidden** | whether timepicker is hidden or not |
-| **nowDelay** | override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. |
-| **quick_ranges** | custom quick ranges |
-| **refresh_intervals** | interval options available in the refresh picker dropdown |
-| **status** | |
-| **type** | |
+
+
+| Name | Usage |
+| --------------------- | --------------------------------------------------------- |
+| **collapse** | whether timepicker is collapsed or not |
+| **enable** | whether timepicker is enabled or not |
+| **notice** | |
+| **now** | |
+| **hidden** | whether timepicker is hidden or not |
+| **nowDelay** | override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. |
+| **quick_ranges** | custom quick ranges |
+| **refresh_intervals** | interval options available in the refresh picker dropdown |
+| **status** | |
+| **type** | |
+
+
### templating
@@ -270,3 +308,82 @@ Usage of the above mentioned fields in the templating section is explained below
| **refresh** | configures when to refresh a variable |
| **regex** | extracts part of a series name or metric node segment |
| **type** | type of variable, i.e. `custom`, `query` or `interval` |
+
+## V1 Resource model
+
+The V1 Resource schema model formats the [Classic JSON model](#classic-model) schema as a Kubernetes-style resource.
+The `spec` property of the schema contains the Classic-style model of the schema.
+
+Dashboards created using the Classic model can be exported using either this model or the Classic one.
+
+The following code snippet shows the fields included in the V1 Resource model.
+
+```json
+{
+ "apiVersion": "dashboard.grafana.app/v1beta1",
+ "kind": "Dashboard",
+ "metadata": {
+ "name": "isnt5ss",
+ "namespace": "stacks-521104",
+ "uid": "92674c0e-0360-4bb4-99ab-fb150581376d",
+ "resourceVersion": "1764705030717045",
+ "generation": 1,
+ "creationTimestamp": "2025-12-02T19:50:30Z",
+ "labels": {
+ "grafana.app/deprecatedInternalID": "1329"
+ },
+ "annotations": {
+ "grafana.app/createdBy": "user:u000000002",
+ "grafana.app/folder": "",
+ "grafana.app/saved-from-ui": "Grafana Cloud (instant)"
+ }
+ },
+ "spec": {
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": 1329,
+ "links": [],
+ "panels": [],
+ "preload": false,
+ "schemaVersion": 42,
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "Africa/Abidjan",
+ "title": "Graphite suggestions",
+ "uid": "isnt5ss",
+ "version": 1,
+ "weekStart": ""
+ },
+ "status": {}
+}
+```
+
+## V2 Resource model
+
+{{< docs/public-preview product="Dashboard JSON schema v2" >}}
+
+For the detailed V2 Resource model schema, refer to the [Swagger documentation](https://play.grafana.org/swagger?api=dashboard.grafana.app-v2beta1).
diff --git a/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md b/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md
index e7749ba5b88..7087b3ac692 100644
--- a/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md
+++ b/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md
@@ -213,7 +213,7 @@ To export a dashboard in its current state as a PDF, follow these steps:
1. Click **Dashboards** in the main menu.
1. Open the dashboard you want to export.
-1. Click the **Export** drop-down in the top-right corner and select **Export as PDF**.
+1. Click the **Export** drop-down in the sidebar and select **Export as PDF**.
1. In the **Export dashboard PDF** drawer that opens, select either **Landscape** or **Portrait** for the PDF orientation.
1. Select either **Grid** or **Simple** for the PDF layout.
1. Set the **Zoom** level; zoom in to enlarge text, or zoom out to see more data (like table columns) per panel.
@@ -229,7 +229,7 @@ Export a Grafana JSON file that contains everything you need, including layout,
1. Click **Dashboards** in the main menu.
1. Open the dashboard you want to export.
-1. Click the **Export** drop-down list in the top-right corner and select **Export as code**.
+1. Click the **Export** drop-down list in the sidebar and select **Export as code**.
The **Export dashboard** drawer opens.
@@ -255,7 +255,7 @@ To export a dashboard in its current state as a PNG image file, follow these ste
1. Click **Dashboards** in the main menu.
1. Open the dashboard you want to export.
-1. Click the **Export** drop-down list in the top-right corner and select **Export as image**.
+1. Click the **Export** drop-down list in the sidebar and select **Export as image**.
The **Export as image** drawer opens.
diff --git a/docs/sources/visualizations/dashboards/use-dashboards/index.md b/docs/sources/visualizations/dashboards/use-dashboards/index.md
index 108d6bc8136..ca9513885bf 100644
--- a/docs/sources/visualizations/dashboards/use-dashboards/index.md
+++ b/docs/sources/visualizations/dashboards/use-dashboards/index.md
@@ -21,67 +21,144 @@ menuTitle: Use dashboards
title: Use dashboards
description: Learn about the features of a Grafana dashboard
weight: 100
-refs:
- dashboard-analytics:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/assess-dashboard-usage/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/assess-dashboard-usage/
- generative-ai-features:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
- dashboard-settings:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/build-dashboards/modify-dashboard-settings/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/modify-dashboard-settings/
- repeating-rows:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/build-dashboards/create-dashboard/#configure-repeating-rows
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/create-dashboard/#configure-repeating-rows
- variables:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/variables/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/variables/
- dashboard-folders:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/manage-dashboards/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/manage-dashboards/
- sharing:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/share-dashboards-panels/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/share-dashboards-panels/
- dashboard-links:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/build-dashboards/manage-dashboard-links/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/manage-dashboard-links/
- panel-overview:
- - pattern: /docs/grafana/
- destination: /docs/grafana//panels-visualizations/panel-overview/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/panels-visualizations/panel-overview/
- export-dashboards:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/share-dashboards-panels/#export-dashboards
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/share-dashboards-panels/#export-dashboards
- add-ad-hoc-filters:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/variables/add-template-variables/#add-ad-hoc-filters
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/variables/add-template-variables/#add-ad-hoc-filters
- shared-dashboards:
- - pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/share-dashboards-panels/shared-dashboards/
- - pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/share-dashboards-panels/shared-dashboards/
+image_maps:
+ - key: annotated-dashboard
+ src: /media/docs/grafana/dashboards/screenshot-ann-dashboards-v12.4.png
+ alt: An annotated image of a Grafana dashboard
+ points:
+ - x_coord: 8
+ y_coord: 5
+ content: |
+ **Dashboard folder**
+
+ Click the dashboard folder name to access the folder and perform other [folder management tasks](https://grafana.com/docs/grafana//visualizations/dashboards/manage-dashboards/).
+ - x_coord: 17
+ y_coord: 5
+ content: |
+ **Dashboard title**
+
+ Create your own dashboard titles or have Grafana create them for you using [generative AI features](https://grafana.com/docs/grafana//visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards).
+ - x_coord: 23
+ y_coord: 5
+ content: |
+ **Mark as favorite**
+
+ Mark the dashboard as one of your favorites to include it in your list of **Starred** dashboards in the main menu.
+ - x_coord: 27
+ y_coord: 5
+ content: |
+ **Public label**
+
+ [Externally shared dashboards](https://grafana.com/docs/grafana//visualizations/dashboards/share-dashboards-panels/shared-dashboards/), it's marked with the **Public** label.
+ - x_coord: 84
+ y_coord: 5
+ content: |
+ **Grafana Assistant**
+
+ [Grafana Assistant](https://grafana.com/docs/grafana-cloud/machine-learning/assistant/introduction/) combines large language models with Grafana-integrated tools.
+ - x_coord: 89
+ y_coord: 5
+ content: |
+ **Invite new users**
+
+ Invite new users to join your Grafana organization.
+ - x_coord: 32
+ y_coord: 23
+ content: |
+ **Variables**
+
+ Use [variables](https://grafana.com/docs/grafana//visualizations/dashboards/variables/), including ad hoc filters, to create more interactive and dynamic dashboards.
+ - x_coord: 45
+ y_coord: 23
+ content: |
+ **Dashboard links**
+
+ Link to other dashboards, panels, and external websites. Learn more about [dashboard links](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/manage-dashboard-links/).
+ - x_coord: 59
+ y_coord: 29
+ content: |
+ **Current dashboard time range and time picker**
+
+ Select [relative time range](#relative-time-range) options or set custom [absolute time ranges](#absolute-time-range).
+ You can also change the **Timezone** and **Fiscal year** settings by clicking the **Change time settings** button.
+ - x_coord: 67
+ y_coord: 29
+ content: |
+ **Time range zoom out**
+
+ Click to zoom out the time range. Learn more about [common time range controls](#common-time-range-controls).
+ - x_coord: 73
+ y_coord: 29
+ content: |
+ **Refresh dashboard**
+
+ Trigger queries and refresh dashboard data.
+ - x_coord: 78
+ y_coord: 29
+ content: |
+ **Auto refresh control**
+
+ Select a dashboard auto refresh time interval.
+ - x_coord: 85
+ y_coord: 29
+ content: |
+ **Share dashboard**
+
+ Access [dashboard sharing](https://grafana.com/docs/grafana//visualizations/dashboards/share-dashboards-panels/) options.
+ - x_coord: 98
+ y_coord: 22.5
+ content: |
+ **Edit**
+
+ Enter edit mode, so you can make changes and access dashboard settings.
+ - x_coord: 98
+ y_coord: 31
+ content: |
+ **Export**
+
+ Access [dashboard exporting](https://grafana.com/docs/grafana//visualizations/dashboards/share-dashboards-panels/#export-dashboards) options.
+ - x_coord: 98
+ y_coord: 39
+ content: |
+ **Content outline**
+
+ The outline provides a tree-like structure that lets you quickly navigate the dashboard.
+ - x_coord: 98
+ y_coord: 47
+ content: |
+ **Dashboard insights**
+
+ View [dashboard analytics](https://grafana.com/docs/grafana//visualizations/dashboards/assess-dashboard-usage/) including information about users, activity, query counts.
+ - x_coord: 11.5
+ y_coord: 30
+ content: |
+ **Row title**
+
+ A row is one way you can [group panels](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/create-dashboard/#panel-groupings) in a dashboard.
+ - x_coord: 20
+ y_coord: 36
+ content: |
+ **Tab title**
+
+ A tab is one way you can [group panels](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/create-dashboard/#panel-groupings) in a dashboard.
+ - x_coord: 21
+ y_coord: 45
+ content: |
+ **Panel title**
+
+ Create your own panel titles or have Grafana create them for you using [generative AI features](https://grafana.com/docs/grafana//visualizations/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards).
+ - x_coord: 27
+ y_coord: 63
+ content: |
+ **Dashboard panel**
+
+ The [panel](https://grafana.com/docs/grafana//panels-visualizations/panel-overview/) is the primary building block of a dashboard.
+ - x_coord: 19.5
+ y_coord: 91
+ content: |
+ **Panel legend**
+
+ Change series colors as well as y-axis and series visibility directly from the legend.
---
# Use dashboards
@@ -95,32 +172,9 @@ This topic provides an overview of dashboard features and shortcuts, and describ
The dashboard user interface provides a number of features that you can use to customize the presentation of your data.
The following image and descriptions highlight all dashboard features.
+Hover your cursor over a number to display information about the dashboard element.
-
-
-1. **Dashboard folder** - When you click the dashboard folder name, you can search for other dashboards contained in the folder and perform other [folder management tasks](ref:dashboard-folders).
-1. **Dashboard title** - You can create your own dashboard titles or have Grafana create them for you using [generative AI features](ref:generative-ai-features).
-1. **Kiosk mode** - Click to display the dashboard on a large screen such as a TV or a kiosk. Kiosk mode hides the main menu, navbar, and dashboard controls. Learn more about kiosk mode in our [How to Create Kiosks to Display Dashboards on a TV blog post](https://grafana.com/blog/2019/05/02/grafana-tutorial-how-to-create-kiosks-to-display-dashboards-on-a-tv/). Press `Esc` to leave kiosk mode.
-1. **Mark as favorite** - Mark the dashboard as one of your favorites so it's included in your list of **Starred** dashboards in the main menu.
-1. **Public label** - When you [share a dashboard externally](ref:shared-dashboards), it's marked with the **Public** label.
-1. **Dashboard insights** - Click to view analytics about your dashboard including information about users, activity, query counts. Learn more about [dashboard analytics](ref:dashboard-analytics).
-1. **Edit** - Click to leave view-only mode and enter edit mode, where you can make changes directly to the dashboard and access dashboard settings, as well as several panel editing functions.
-1. **Export** - Access [dashboard exporting](ref:export-dashboards) options.
-1. **Share dashboard** - Access several [dashboard sharing](ref:sharing) options.
-1. **Variables** - Use [variables](ref:variables), including ad hoc filters, to create more interactive and dynamic dashboards.
-1. **Dashboard links** - Link to other dashboards, panels, and external websites. Learn more about [dashboard links](ref:dashboard-links).
-1. **Current dashboard time range and time picker** - Click to select [relative time range](#relative-time-range) options and set custom [absolute time ranges](#absolute-time-range).
- - You can change the **Timezone** and **Fiscal year** settings from the time range controls by clicking the **Change time settings** button.
- - Time settings are saved on a per-dashboard basis.
-1. **Time range zoom out** - Click to zoom out the time range. Learn more about how to use [common time range controls](#common-time-range-controls).
-1. **Refresh dashboard** - Click to immediately trigger queries and refresh dashboard data.
-1. **Auto refresh control** - Click to select a dashboard auto refresh time interval.
-1. **Dashboard row** - A dashboard row is a logical divider within a dashboard that groups panels together.
- - Rows can be collapsed or expanded allowing you to hide parts of the dashboard.
- - Panels inside a collapsed row do not issue queries.
- - Use [repeating rows](ref:repeating-rows) to dynamically create rows based on a template variable.
-1. **Dashboard panel** - The [panel](ref:panel-overview) is the primary building block of a dashboard.
-1. **Panel legend** - Change series colors as well as y-axis and series visibility directly from the legend.
+{{< image-map key="annotated-dashboard" >}}
## Keyboard shortcuts
@@ -134,7 +188,7 @@ Grafana has a number of keyboard shortcuts available. Press `?` on your keyboard
- `Ctrl+K`: Opens the command palette.
- `Esc`: Exits panel when in full screen view or edit mode. Also returns you to the dashboard from dashboard settings.
-**Focused panel**
+### Focused panel
By hovering over a panel with the mouse you can use some shortcuts that will target that panel.
@@ -285,7 +339,7 @@ Selecting the **Auto** interval schedules a refresh based on the query time rang
## Filter dashboard data
-Once you've [added an ad hoc filter](ref:add-ad-hoc-filters) in the dashboard settings, you can create label/value filter pairs on the dashboard.
+Once you've [added an ad hoc filter](https://grafana.com/docs/grafana//visualizations/dashboards/variables/add-template-variables/#add-ad-hoc-filters) in the dashboard settings, you can create label/value filter pairs on the dashboard.
These filters are applied to all metric queries that use the specified data source and to all panels on the dashboard.
To filter dashboard data, follow these steps:
diff --git a/docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md b/docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md
index 3a2185d699a..68d41969eda 100644
--- a/docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md
+++ b/docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md
@@ -35,9 +35,9 @@ refs:
destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/manage-dashboard-links/#panel-links
configure-repeating-rows:
- pattern: /docs/grafana/
- destination: /docs/grafana//dashboards/build-dashboards/create-dashboard/#configure-repeating-rows
+ destination: /docs/grafana//dashboards/build-dashboards/create-dashboard/#configure-repeat-options
- pattern: /docs/grafana-cloud/
- destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/create-dashboard/#configure-repeating-rows
+ destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/create-dashboard/#configure-repeat-options
set-up-generative-ai-features-for-dashboards:
- pattern: /docs/grafana/
destination: /docs/grafana//dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
diff --git a/docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md
index b54326968a6..3a4a448ae85 100644
--- a/docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md
+++ b/docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md
@@ -30,7 +30,9 @@ refs:
# Datagrid
-{{< docs/experimental product="The datagrid visualization" featureFlag="`enableDatagridEditing`" >}}
+{{< admonition type="caution" >}}
+Starting with Grafana 12.4, Datagrid is deprecated. It will be removed in version 13.0.
+{{< /admonition >}}
Datagrids offer you the ability to create, edit, and fine-tune data within Grafana. As such, this panel can act as a data source for other panels
inside a dashboard.
diff --git a/e2e-playwright/alerting-suite/saved-searches.spec.ts b/e2e-playwright/alerting-suite/saved-searches.spec.ts
index 28de805a5a1..b4f6431a56b 100644
--- a/e2e-playwright/alerting-suite/saved-searches.spec.ts
+++ b/e2e-playwright/alerting-suite/saved-searches.spec.ts
@@ -2,6 +2,15 @@ import { Page } from '@playwright/test';
import { test, expect } from '@grafana/plugin-e2e';
+// Enable required feature toggles for Saved Searches (part of RuleList.v2)
+test.use({
+ featureToggles: {
+ alertingListViewV2: true,
+ alertingFilterV2: true,
+ alertingSavedSearches: true,
+ },
+});
+
/**
* UI selectors for Saved Searches e2e tests.
* Each selector is a function that takes the page and returns a locator.
@@ -26,26 +35,50 @@ const ui = {
// Indicators
emptyState: (page: Page) => page.getByText(/no saved searches/i),
- defaultIcon: (page: Page) => page.locator('[title="Default search"]'),
+ defaultIcon: (page: Page) => page.getByRole('img', { name: /default search/i }),
duplicateError: (page: Page) => page.getByText(/already exists/i),
};
/**
- * Helper to clear saved searches storage.
- * UserStorage uses localStorage as fallback, so we clear both potential keys.
+ * Helper to clear saved searches from UserStorage.
+ * UserStorage persists data server-side via k8s API, so we need to delete via API.
*/
async function clearSavedSearches(page: Page) {
- await page.evaluate(() => {
- // Clear localStorage keys that might contain saved searches
- // UserStorage stores under 'grafana.userstorage.alerting' pattern
- const keysToRemove = Object.keys(localStorage).filter(
- (key) => key.includes('alerting') && (key.includes('savedSearches') || key.includes('userstorage'))
- );
- keysToRemove.forEach((key) => localStorage.removeItem(key));
+ // Get namespace and user info from Grafana config
+ const storageInfo = await page.evaluate(() => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const bootData = (window as any).grafanaBootData;
+ const user = bootData?.user;
+ const userUID = user?.uid === '' || !user?.uid ? String(user?.id ?? 'anonymous') : user.uid;
+ const resourceName = `alerting:${userUID}`;
+ const namespace = bootData?.settings?.namespace || 'default';
- // Also clear session storage visited flag
- const sessionKeysToRemove = Object.keys(sessionStorage).filter((key) => key.includes('alerting'));
- sessionKeysToRemove.forEach((key) => sessionStorage.removeItem(key));
+ return { namespace, resourceName };
+ });
+
+ // Delete the UserStorage resource
+ try {
+ await page.request.delete(
+ `/apis/userstorage.grafana.app/v0alpha1/namespaces/${storageInfo.namespace}/user-storage/${storageInfo.resourceName}`
+ );
+ } catch (error) {
+ // Ignore 404 errors (resource doesn't exist)
+ if (!(error && typeof error === 'object' && 'status' in error && error.status === 404)) {
+ console.warn('Failed to clear saved searches:', error);
+ }
+ }
+
+ // Also clear localStorage as fallback storage
+ await page.evaluate(({ resourceName }) => {
+ // The UserStorage key pattern is always `{resourceName}:{key}`
+ // For saved searches, the key is 'savedSearches'
+ const key = `${resourceName}:savedSearches`;
+ window.localStorage.removeItem(key);
+ }, storageInfo);
+
+ // Clear session storage visited flag
+ await page.evaluate(() => {
+ window.sessionStorage.removeItem('grafana.alerting.ruleList.visited');
});
}
@@ -150,7 +183,7 @@ test.describe(
await ui.saveButton(page).click();
- await ui.saveNameInput(page).fill('Apply Test');
+ await ui.saveNameInput(page).fill('Firing Rules');
await ui.saveConfirmButton(page).click();
// Clear the search
@@ -159,7 +192,7 @@ test.describe(
// Apply the saved search
await ui.savedSearchesButton(page).click();
- await page.getByRole('button', { name: /apply search.*apply test/i }).click();
+ await page.getByRole('button', { name: /apply.*search.*firing rules/i }).click();
// Verify the search input is updated
await expect(ui.searchInput(page)).toHaveValue('state:firing');
@@ -182,7 +215,7 @@ test.describe(
await ui.renameMenuItem(page).click();
// Enter new name
- const renameInput = page.getByDisplayValue('Original Name');
+ const renameInput = page.getByRole('textbox', { name: /enter a name/i });
await renameInput.clear();
await renameInput.fill('Renamed Search');
await page.keyboard.press('Enter');
@@ -260,12 +293,12 @@ test.describe(
await expect(ui.saveNameInput(page)).toBeVisible();
- // Press Escape to cancel
+ // Press Escape to cancel - this closes the entire dropdown
await page.keyboard.press('Escape');
- // Verify we're back to list mode
- await expect(ui.saveNameInput(page)).not.toBeVisible();
- await expect(ui.saveButton(page)).toBeVisible();
+ // Verify the entire dialog is closed
+ await expect(ui.dropdown(page)).not.toBeVisible();
+ await expect(ui.saveButton(page)).not.toBeVisible();
});
}
);
diff --git a/e2e-playwright/dashboard-cujs/adhoc-filters-cujs.spec.ts b/e2e-playwright/dashboard-cujs/adhoc-filters-cujs.spec.ts
index 038fe099eeb..2a0f6932141 100644
--- a/e2e-playwright/dashboard-cujs/adhoc-filters-cujs.spec.ts
+++ b/e2e-playwright/dashboard-cujs/adhoc-filters-cujs.spec.ts
@@ -1,6 +1,7 @@
import { test, expect } from '@grafana/plugin-e2e';
-import { setScopes } from '../utils/scope-helpers';
+import { setScopes, setupScopeRoutes } from '../utils/scope-helpers';
+import { testScopes } from '../utils/scopes';
import {
getAdHocFilterOptionValues,
@@ -13,6 +14,7 @@ import {
} from './cuj-selectors';
import { prepareAPIMocks } from './utils';
+const USE_LIVE_DATA = Boolean(process.env.API_CONFIG_PATH);
const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
test.use({
@@ -34,6 +36,11 @@ test.describe(
const adHocFilterPills = getAdHocFilterPills(page);
const scopesSelectorInput = getScopesSelectorInput(page);
+ // Set up routes before any navigation (only for mocked mode)
+ if (!USE_LIVE_DATA) {
+ await setupScopeRoutes(page, testScopes());
+ }
+
await test.step('1.Apply filtering to a whole dashboard', async () => {
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
diff --git a/e2e-playwright/dashboard-cujs/cuj-selectors.ts b/e2e-playwright/dashboard-cujs/cuj-selectors.ts
index f2366fd2ef1..548c183ef3f 100644
--- a/e2e-playwright/dashboard-cujs/cuj-selectors.ts
+++ b/e2e-playwright/dashboard-cujs/cuj-selectors.ts
@@ -66,6 +66,17 @@ export function getScopesDashboards(page: Page) {
return page.locator('[data-testid^="scopes-dashboards-"][role="treeitem"]');
}
+/**
+ * Clicks the first available dashboard in the scopes dashboard list.
+ */
+export async function clickFirstScopesDashboard(page: Page) {
+ const dashboards = getScopesDashboards(page);
+ // Wait for at least one dashboard to be visible
+ await expect(dashboards.first()).toBeVisible({ timeout: 10000 });
+ // Click - Playwright will automatically wait for the element to be actionable
+ await dashboards.first().click();
+}
+
export function getScopesDashboardsSearchInput(page: Page) {
return page.getByTestId('scopes-dashboards-search');
}
diff --git a/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts b/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts
index 008e1e5538c..c941c40f6ef 100644
--- a/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts
+++ b/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts
@@ -1,8 +1,10 @@
import { test, expect } from '@grafana/plugin-e2e';
-import { setScopes } from '../utils/scope-helpers';
+import { setScopes, setupScopeRoutes } from '../utils/scope-helpers';
+import { testScopes } from '../utils/scopes';
import {
+ clickFirstScopesDashboard,
getAdHocFilterPills,
getGroupByInput,
getGroupByValues,
@@ -21,6 +23,7 @@ test.use({
},
});
+const USE_LIVE_DATA = Boolean(process.env.API_CONFIG_PATH);
const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
const DASHBOARD_UNDER_TEST_2 = 'cuj-dashboard-2';
const NAVIGATE_TO = 'cuj-dashboard-3';
@@ -38,6 +41,11 @@ test.describe(
const adhocFilterPills = getAdHocFilterPills(page);
const groupByValues = getGroupByValues(page);
+ // Set up routes before any navigation (only for mocked mode)
+ if (!USE_LIVE_DATA) {
+ await setupScopeRoutes(page, testScopes());
+ }
+
await test.step('1.Search dashboard', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
@@ -74,7 +82,7 @@ test.describe(
await expect(markdownContent).toContainText(`now-12h`);
- await scopesDashboards.first().click();
+ await clickFirstScopesDashboard(page);
await page.waitForURL('**/d/**');
await expect(markdownContent).toBeVisible();
@@ -117,10 +125,10 @@ test.describe(
await groupByVariable.press('Enter');
await groupByVariable.press('Escape');
- await expect(scopesDashboards.first()).toBeVisible();
-
const { getRequests, waitForExpectedRequests } = await trackDashboardReloadRequests(page);
- await scopesDashboards.first().click();
+
+ await clickFirstScopesDashboard(page);
+ await page.waitForURL('**/d/**');
await waitForExpectedRequests();
await page.waitForLoadState('networkidle');
@@ -158,8 +166,7 @@ test.describe(
const oldFilters = `GroupByVar: ${selectedValues}\n\nAdHocVar: ${processedPills}`;
await expect(markdownContent).toContainText(oldFilters);
- await expect(scopesDashboards.first()).toBeVisible();
- await scopesDashboards.first().click();
+ await clickFirstScopesDashboard(page);
await page.waitForURL('**/d/**');
const newPillCount = await adhocFilterPills.count();
diff --git a/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts b/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts
index 53dd02a0314..e9c1370bbc6 100644
--- a/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts
+++ b/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts
@@ -165,9 +165,8 @@ test.describe(
await refreshBtn.click();
- await page.waitForLoadState('networkidle');
-
- expect(await panelContent.textContent()).not.toBe(panelContents);
+ // Wait for the panel content to change (not just for network to complete)
+ await expect(panelContent).not.toHaveText(panelContents!, { timeout: 10000 });
});
await test.step('6.Turn off refresh', async () => {
diff --git a/e2e-playwright/dashboard-cujs/scope-cujs.spec.ts b/e2e-playwright/dashboard-cujs/scope-cujs.spec.ts
index 54ec3ca7a8b..dd1cd50c35f 100644
--- a/e2e-playwright/dashboard-cujs/scope-cujs.spec.ts
+++ b/e2e-playwright/dashboard-cujs/scope-cujs.spec.ts
@@ -9,6 +9,7 @@ import {
openScopesSelector,
searchScopes,
selectScope,
+ setupScopeRoutes,
} from '../utils/scope-helpers';
import { testScopes } from '../utils/scopes';
@@ -36,32 +37,37 @@ test.describe(
const scopesSelector = getScopesSelectorInput(page);
const recentScopesSelector = getRecentScopesSelector(page);
const scopeTreeCheckboxes = getScopeTreeCheckboxes(page);
+ const scopes = testScopes();
+
+ // Set up routes once before any navigation (only for mocked mode)
+ if (!USE_LIVE_DATA) {
+ await setupScopeRoutes(page, scopes);
+ }
await test.step('1.View and select any scope', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
expect.soft(scopesSelector).toHaveAttribute('data-value', '');
- const scopes = testScopes();
- await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
+ await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes);
let scopeName = await getScopeTreeName(page, 0);
- const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
+ const firstLevelScopes = scopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
scopeName = await getScopeTreeName(page, 1);
- const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
+ const secondLevelScopes = firstLevelScopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
- const selectedScopes = [secondLevelScopes[0]]; //used only in mocked scopes version
+ const selectedScopes = [secondLevelScopes[0]];
scopeName = await getScopeLeafName(page, 0);
let scopeTitle = await getScopeLeafTitle(page, 0);
await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[0]);
- await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes); //used only in mocked scopes version
+ await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes);
expect.soft(scopesSelector).toHaveAttribute('data-value', scopeTitle);
});
@@ -70,28 +76,27 @@ test.describe(
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
expect.soft(scopesSelector).toHaveAttribute('data-value', '');
- const scopes = testScopes();
- await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
+ await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes);
let scopeName = await getScopeTreeName(page, 0);
- const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
+ const firstLevelScopes = scopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
scopeName = await getScopeTreeName(page, 1);
- const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
+ const secondLevelScopes = firstLevelScopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
const scopeTitles: string[] = [];
- const selectedScopes = [secondLevelScopes[0], secondLevelScopes[1]]; //used only in mocked scopes version
+ const selectedScopes = [secondLevelScopes[0], secondLevelScopes[1]];
for (let i = 0; i < selectedScopes.length; i++) {
scopeName = await getScopeLeafName(page, i);
scopeTitles.push(await getScopeLeafTitle(page, i));
- await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[i]); //used only in mocked scopes version
+ await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[i]);
}
- await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes); //used only in mocked scopes version
+ await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes);
await expect.soft(scopesSelector).toHaveAttribute('data-value', scopeTitles.join(' + '));
});
@@ -102,8 +107,7 @@ test.describe(
expect.soft(scopesSelector).toHaveAttribute('data-value', '');
- const scopes = testScopes();
- await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
+ await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes);
await recentScopesSelector.click();
@@ -121,26 +125,25 @@ test.describe(
expect.soft(scopesSelector).toHaveAttribute('data-value', '');
- const scopes = testScopes();
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes);
let scopeName = await getScopeTreeName(page, 1);
- const firstLevelScopes = scopes[2].children!; //used only in mocked scopes version
+ const firstLevelScopes = scopes[2].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
scopeName = await getScopeTreeName(page, 1);
- const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
+ const secondLevelScopes = firstLevelScopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
- const selectedScopes = [secondLevelScopes[0]]; //used only in mocked scopes version
+ const selectedScopes = [secondLevelScopes[0]];
scopeName = await getScopeLeafName(page, 0);
let scopeTitle = await getScopeLeafTitle(page, 0);
await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[0]);
- await applyScopes(page, USE_LIVE_DATA ? undefined : []); //used only in mocked scopes version
+ await applyScopes(page, USE_LIVE_DATA ? undefined : []);
expect.soft(scopesSelector).toHaveAttribute('data-value', new RegExp(`^${scopeTitle}`));
});
@@ -148,17 +151,16 @@ test.describe(
await test.step('5.View pre-completed production entity values as I type', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
- const scopes = testScopes();
- await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
+ await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes);
let scopeName = await getScopeTreeName(page, 0);
- const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
+ const firstLevelScopes = scopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
scopeName = await getScopeTreeName(page, 1);
- const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
+ const secondLevelScopes = firstLevelScopes[0].children!;
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
const scopeSearchOne = await getScopeLeafTitle(page, 0);
diff --git a/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts b/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts
index 952e8a3da63..b140e7a9838 100644
--- a/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts
+++ b/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts
@@ -1,6 +1,6 @@
import { test, expect } from '@grafana/plugin-e2e';
-import { applyScopes, openScopesSelector, selectScope } from '../utils/scope-helpers';
+import { applyScopes, openScopesSelector, selectScope, setupScopeRoutes } from '../utils/scope-helpers';
import { testScopesWithRedirect } from '../utils/scopes';
test.use({
@@ -16,8 +16,13 @@ test.describe('Scope Redirect Functionality', () => {
test('should redirect to custom URL when scope has redirectUrl', async ({ page, gotoDashboardPage }) => {
const scopes = testScopesWithRedirect();
- await test.step('Navigate to dashboard and open scopes selector', async () => {
+ await test.step('Set up routes and navigate to dashboard', async () => {
+ // Set up routes BEFORE navigation to ensure all requests are mocked
+ await setupScopeRoutes(page, scopes);
await gotoDashboardPage({ uid: 'cuj-dashboard-1' });
+ });
+
+ await test.step('Open scopes selector', async () => {
await openScopesSelector(page, scopes);
});
@@ -40,8 +45,12 @@ test.describe('Scope Redirect Functionality', () => {
test('should prioritize redirectUrl over scope navigation fallback', async ({ page, gotoDashboardPage }) => {
const scopes = testScopesWithRedirect();
- await test.step('Navigate to dashboard and open scopes selector', async () => {
+ await test.step('Set up routes and navigate to dashboard', async () => {
+ await setupScopeRoutes(page, scopes);
await gotoDashboardPage({ uid: 'cuj-dashboard-1' });
+ });
+
+ await test.step('Open scopes selector', async () => {
await openScopesSelector(page, scopes);
});
@@ -68,8 +77,12 @@ test.describe('Scope Redirect Functionality', () => {
}) => {
const scopes = testScopesWithRedirect();
- await test.step('Navigate to dashboard and select scope', async () => {
+ await test.step('Set up routes and navigate to dashboard', async () => {
+ await setupScopeRoutes(page, scopes);
await gotoDashboardPage({ uid: 'cuj-dashboard-1' });
+ });
+
+ await test.step('Select and apply scope', async () => {
await openScopesSelector(page, scopes);
await selectScope(page, 'sn-redirect-fallback', scopes[1]);
await applyScopes(page, [scopes[1]]);
@@ -112,8 +125,12 @@ test.describe('Scope Redirect Functionality', () => {
}) => {
const scopes = testScopesWithRedirect();
- await test.step('Navigate to dashboard and select scope', async () => {
+ await test.step('Set up routes and navigate to dashboard', async () => {
+ await setupScopeRoutes(page, scopes);
await gotoDashboardPage({ uid: 'cuj-dashboard-1' });
+ });
+
+ await test.step('Select and apply scope', async () => {
await openScopesSelector(page, scopes);
await selectScope(page, 'sn-redirect-fallback', scopes[1]);
await applyScopes(page, [scopes[1]]);
@@ -151,9 +168,13 @@ test.describe('Scope Redirect Functionality', () => {
test('should not redirect to redirectPath when on active scope navigation', async ({ page, gotoDashboardPage }) => {
const scopes = testScopesWithRedirect();
+ await test.step('Set up routes and navigate to dashboard', async () => {
+ await setupScopeRoutes(page, scopes);
+ await gotoDashboardPage({ uid: 'cuj-dashboard-1' });
+ });
+
await test.step('Set up scope navigation to dashboard-1', async () => {
// First, apply a scope that creates scope navigation to dashboard-1 (without redirectPath)
- await gotoDashboardPage({ uid: 'cuj-dashboard-1' });
await openScopesSelector(page, scopes);
await selectScope(page, 'sn-redirect-setup', scopes[2]);
await applyScopes(page, [scopes[2]]);
diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-repeats-custom-grid.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-repeats-custom-grid.spec.ts
index 8cc1f552377..e60b722c46e 100644
--- a/e2e-playwright/dashboard-new-layouts/dashboards-repeats-custom-grid.spec.ts
+++ b/e2e-playwright/dashboard-new-layouts/dashboards-repeats-custom-grid.spec.ts
@@ -1,6 +1,7 @@
import { test, expect } from '@grafana/plugin-e2e';
import testV2DashWithRepeats from '../dashboards/V2DashWithRepeats.json';
+import testV2DashWithRowRepeats from '../dashboards/V2DashWithRowRepeats.json';
import {
checkRepeatedPanelTitles,
@@ -10,11 +11,14 @@ import {
saveDashboard,
importTestDashboard,
goToEmbeddedPanel,
+ goToPanelSnapshot,
} from './utils';
const repeatTitleBase = 'repeat - ';
const newTitleBase = 'edited rep - ';
const repeatOptions = [1, 2, 3, 4];
+const getTitleInRepeatRow = (rowIndex: number, panelIndex: number) =>
+ `repeated-row-${rowIndex}-repeated-panel-${panelIndex}`;
test.use({
featureToggles: {
@@ -165,9 +169,7 @@ test.describe(
)
).toBeVisible();
- await dashboardPage
- .getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.backToDashboardButton)
- .click();
+ await page.keyboard.press('Escape');
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.DashboardEditPaneSplitter.primaryBody)
@@ -217,9 +219,7 @@ test.describe(
)
).toBeVisible();
- await dashboardPage
- .getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.backToDashboardButton)
- .click();
+ await page.keyboard.press('Escape');
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.DashboardEditPaneSplitter.primaryBody)
@@ -405,5 +405,143 @@ test.describe(
await dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer).all()
).toHaveLength(3);
});
+
+ test('can view repeated panel in a repeated row', async ({ dashboardPage, selectors, page }) => {
+ await importTestDashboard(
+ page,
+ selectors,
+ 'Custom grid repeats - view repeated panel in a repeated row',
+ JSON.stringify(testV2DashWithRowRepeats)
+ );
+
+ // make sure the repeated panel is present in multiple rows
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ ).toBeVisible();
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(2, 2)))
+ ).toBeVisible();
+
+ await dashboardPage
+ .getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ .hover();
+
+ await page.keyboard.press('v');
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(2, 2)))
+ ).not.toBeVisible();
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ ).toBeVisible();
+
+ const repeatedPanelUrl = page.url();
+
+ await page.keyboard.press('Escape');
+
+ // load view panel directly
+ await page.goto(repeatedPanelUrl);
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ ).toBeVisible();
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(2, 2)))
+ ).not.toBeVisible();
+ });
+
+ test('can view embedded panel in a repeated row', async ({ dashboardPage, selectors, page }) => {
+ const embedPanelTitle = 'embedded-panel';
+ await importTestDashboard(
+ page,
+ selectors,
+ 'Custom grid repeats - view embedded repeated panel in a repeated row',
+ JSON.stringify(testV2DashWithRowRepeats)
+ );
+
+ await dashboardPage
+ .getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ .hover();
+ await page.keyboard.press('p+e');
+
+ await goToEmbeddedPanel(page);
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ ).toBeVisible();
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(2, 2)))
+ ).not.toBeVisible();
+ });
+
+ // there is a bug in the Snapshot feature that prevents the next two tests from passing
+ // tracking issue: https://github.com/grafana/grafana/issues/114509
+ test.skip('can view repeated panel inside snapshot', async ({ dashboardPage, selectors, page }) => {
+ await importTestDashboard(
+ page,
+ selectors,
+ 'Custom grid repeats - view repeated panel inside snapshot',
+ JSON.stringify(testV2DashWithRowRepeats)
+ );
+
+ await dashboardPage
+ .getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ .hover();
+ await page.keyboard.press('p+s');
+
+ // click "Publish snapshot"
+ await dashboardPage
+ .getByGrafanaSelector(selectors.pages.ShareDashboardDrawer.ShareSnapshot.publishSnapshot)
+ .click();
+
+ // click "Copy link" button in the snapshot drawer
+ await dashboardPage
+ .getByGrafanaSelector(selectors.pages.ShareDashboardDrawer.ShareSnapshot.copyUrlButton)
+ .click();
+
+ await goToPanelSnapshot(page);
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ ).toBeVisible();
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(2, 2)))
+ ).not.toBeVisible();
+ });
+ test.skip('can view single panel in a repeated row inside snapshot', async ({ dashboardPage, selectors, page }) => {
+ await importTestDashboard(
+ page,
+ selectors,
+ 'Custom grid repeats - view single panel inside snapshot',
+ JSON.stringify(testV2DashWithRowRepeats)
+ );
+
+ await dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('single panel row 1')).hover();
+ // open panel snapshot
+ await page.keyboard.press('p+s');
+
+ // click "Publish snapshot"
+ await dashboardPage
+ .getByGrafanaSelector(selectors.pages.ShareDashboardDrawer.ShareSnapshot.publishSnapshot)
+ .click();
+
+ // click "Copy link" button
+ await dashboardPage
+ .getByGrafanaSelector(selectors.pages.ShareDashboardDrawer.ShareSnapshot.copyUrlButton)
+ .click();
+
+ await goToPanelSnapshot(page);
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('single panel row 1'))
+ ).toBeVisible();
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(getTitleInRepeatRow(1, 1)))
+ ).toBeHidden();
+ });
}
);
diff --git a/e2e-playwright/dashboard-new-layouts/utils.ts b/e2e-playwright/dashboard-new-layouts/utils.ts
index ade6825b7c1..d2f196f1466 100644
--- a/e2e-playwright/dashboard-new-layouts/utils.ts
+++ b/e2e-playwright/dashboard-new-layouts/utils.ts
@@ -218,6 +218,15 @@ export async function goToEmbeddedPanel(page: Page) {
await page.goto(soloPanelUrl!);
}
+export async function goToPanelSnapshot(page: Page) {
+ // extracting snapshot url from clipboard
+ const snapshotUrl = await page.evaluate(() => navigator.clipboard.readText());
+
+ expect(snapshotUrl).toBeDefined();
+
+ await page.goto(snapshotUrl);
+}
+
export async function moveTab(
dashboardPage: DashboardPage,
page: Page,
diff --git a/e2e-playwright/dashboards/V2DashWithRowRepeats.json b/e2e-playwright/dashboards/V2DashWithRowRepeats.json
new file mode 100644
index 00000000000..2438908b823
--- /dev/null
+++ b/e2e-playwright/dashboards/V2DashWithRowRepeats.json
@@ -0,0 +1,486 @@
+{
+ "apiVersion": "dashboard.grafana.app/v2beta1",
+ "kind": "Dashboard",
+ "metadata": {
+ "name": "ad8l8fz",
+ "namespace": "default",
+ "uid": "fLb2na54K8NZHvn8LfWGL1jhZh03Hy0xpV1KzMYgAXEX",
+ "resourceVersion": "1",
+ "generation": 2,
+ "creationTimestamp": "2025-11-25T15:52:42Z",
+ "labels": {
+ "grafana.app/deprecatedInternalID": "20"
+ },
+ "annotations": {
+ "grafana.app/createdBy": "user:aerwo725ot62od",
+ "grafana.app/updatedBy": "user:aerwo725ot62od",
+ "grafana.app/updatedTimestamp": "2025-11-25T15:52:42Z",
+ "grafana.app/folder": ""
+ }
+ },
+ "spec": {
+ "annotations": [
+ {
+ "kind": "AnnotationQuery",
+ "spec": {
+ "builtIn": true,
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "query": {
+ "datasource": {
+ "name": "-- Grafana --"
+ },
+ "group": "grafana",
+ "kind": "DataQuery",
+ "spec": {},
+ "version": "v0"
+ }
+ }
+ }
+ ],
+ "cursorSync": "Off",
+ "description": "",
+ "editable": true,
+ "elements": {
+ "panel-1": {
+ "kind": "Panel",
+ "spec": {
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "hidden": false,
+ "query": {
+ "group": "",
+ "kind": "DataQuery",
+ "spec": {},
+ "version": "v0"
+ },
+ "refId": "A"
+ }
+ }
+ ],
+ "queryOptions": {},
+ "transformations": []
+ }
+ },
+ "description": "",
+ "id": 4,
+ "links": [],
+ "title": "repeated-row-$c4-repeated-panel-$c3",
+ "vizConfig": {
+ "group": "timeseries",
+ "kind": "VizConfig",
+ "spec": {
+ "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"
+ }
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ }
+ },
+ "version": "12.4.0-pre"
+ }
+ }
+ },
+ "panel-2": {
+ "kind": "Panel",
+ "spec": {
+ "data": {
+ "kind": "QueryGroup",
+ "spec": {
+ "queries": [
+ {
+ "kind": "PanelQuery",
+ "spec": {
+ "hidden": false,
+ "query": {
+ "group": "",
+ "kind": "DataQuery",
+ "spec": {},
+ "version": "v0"
+ },
+ "refId": "A"
+ }
+ }
+ ],
+ "queryOptions": {},
+ "transformations": []
+ }
+ },
+ "description": "",
+ "id": 2,
+ "links": [],
+ "title": "single panel row $c4",
+ "vizConfig": {
+ "group": "timeseries",
+ "kind": "VizConfig",
+ "spec": {
+ "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"
+ }
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "hideZeros": false,
+ "mode": "single",
+ "sort": "none"
+ }
+ }
+ },
+ "version": "12.4.0-pre"
+ }
+ }
+ }
+ },
+ "layout": {
+ "kind": "RowsLayout",
+ "spec": {
+ "rows": [
+ {
+ "kind": "RowsLayoutRow",
+ "spec": {
+ "collapse": false,
+ "layout": {
+ "kind": "GridLayout",
+ "spec": {
+ "items": [
+ {
+ "kind": "GridLayoutItem",
+ "spec": {
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-1"
+ },
+ "height": 10,
+ "repeat": {
+ "direction": "h",
+ "mode": "variable",
+ "value": "c3"
+ },
+ "width": 24,
+ "x": 0,
+ "y": 0
+ }
+ },
+ {
+ "kind": "GridLayoutItem",
+ "spec": {
+ "element": {
+ "kind": "ElementReference",
+ "name": "panel-2"
+ },
+ "height": 8,
+ "width": 12,
+ "x": 0,
+ "y": 10
+ }
+ }
+ ]
+ }
+ },
+ "repeat": {
+ "mode": "variable",
+ "value": "c4"
+ },
+ "title": "Repeated row $c4"
+ }
+ }
+ ]
+ }
+ },
+ "links": [],
+ "liveNow": false,
+ "preload": false,
+ "tags": [],
+ "timeSettings": {
+ "autoRefresh": "",
+ "autoRefreshIntervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
+ "fiscalYearStartMonth": 0,
+ "from": "now-6h",
+ "hideTimepicker": false,
+ "timezone": "browser",
+ "to": "now"
+ },
+ "title": "test-e2e-repeats",
+ "variables": [
+ {
+ "kind": "CustomVariable",
+ "spec": {
+ "allowCustomValue": true,
+ "current": {
+ "text": ["1", "2", "3", "4"],
+ "value": ["1", "2", "3", "4"]
+ },
+ "hide": "dontHide",
+ "includeAll": true,
+ "multi": true,
+ "name": "c1",
+ "options": [
+ {
+ "selected": true,
+ "text": "1",
+ "value": "1"
+ },
+ {
+ "selected": true,
+ "text": "2",
+ "value": "2"
+ },
+ {
+ "selected": true,
+ "text": "3",
+ "value": "3"
+ },
+ {
+ "selected": true,
+ "text": "4",
+ "value": "4"
+ }
+ ],
+ "query": "1,2,3,4",
+ "skipUrlSync": false
+ }
+ },
+ {
+ "kind": "CustomVariable",
+ "spec": {
+ "allowCustomValue": true,
+ "current": {
+ "text": ["A", "B", "C", "D"],
+ "value": ["A", "B", "C", "D"]
+ },
+ "hide": "dontHide",
+ "includeAll": true,
+ "multi": true,
+ "name": "c2",
+ "options": [
+ {
+ "selected": true,
+ "text": "A",
+ "value": "A"
+ },
+ {
+ "selected": true,
+ "text": "B",
+ "value": "B"
+ },
+ {
+ "selected": true,
+ "text": "C",
+ "value": "C"
+ },
+ {
+ "selected": true,
+ "text": "D",
+ "value": "D"
+ }
+ ],
+ "query": "A,B,C,D",
+ "skipUrlSync": false
+ }
+ },
+ {
+ "kind": "CustomVariable",
+ "spec": {
+ "allowCustomValue": true,
+ "current": {
+ "text": ["1", "2", "3", "4"],
+ "value": ["1", "2", "3", "4"]
+ },
+ "hide": "dontHide",
+ "includeAll": false,
+ "multi": true,
+ "name": "c3",
+ "options": [
+ {
+ "selected": true,
+ "text": "1",
+ "value": "1"
+ },
+ {
+ "selected": true,
+ "text": "2",
+ "value": "2"
+ },
+ {
+ "selected": true,
+ "text": "3",
+ "value": "3"
+ },
+ {
+ "selected": true,
+ "text": "4",
+ "value": "4"
+ }
+ ],
+ "query": "1, 2, 3, 4",
+ "skipUrlSync": false
+ }
+ },
+ {
+ "kind": "CustomVariable",
+ "spec": {
+ "allowCustomValue": true,
+ "current": {
+ "text": ["1", "2", "3", "4"],
+ "value": ["1", "2", "3", "4"]
+ },
+ "hide": "dontHide",
+ "includeAll": false,
+ "multi": true,
+ "name": "c4",
+ "options": [
+ {
+ "selected": true,
+ "text": "1",
+ "value": "1"
+ },
+ {
+ "selected": true,
+ "text": "2",
+ "value": "2"
+ },
+ {
+ "selected": true,
+ "text": "3",
+ "value": "3"
+ },
+ {
+ "selected": true,
+ "text": "4",
+ "value": "4"
+ }
+ ],
+ "query": "1, 2, 3, 4",
+ "skipUrlSync": false
+ }
+ }
+ ]
+ },
+ "status": {}
+}
diff --git a/e2e-playwright/utils/scope-helpers.ts b/e2e-playwright/utils/scope-helpers.ts
index fc88a79d8fa..df11644a396 100644
--- a/e2e-playwright/utils/scope-helpers.ts
+++ b/e2e-playwright/utils/scope-helpers.ts
@@ -6,7 +6,150 @@ import { Resource } from '../../public/app/features/apiserver/types';
import { testScopes } from './scopes';
-const USE_LIVE_DATA = Boolean(process.env.API_CALLS_CONFIG_PATH);
+const USE_LIVE_DATA = Boolean(process.env.API_CONFIG_PATH);
+
+/**
+ * Sets up all scope-related API routes before navigation.
+ * This ensures that ALL scope API requests (including those made during initial page load)
+ * are intercepted by the mocks, preventing RTK Query from caching real API responses.
+ *
+ * Call this BEFORE navigating to a page (e.g., before gotoDashboardPage).
+ */
+export async function setupScopeRoutes(page: Page, scopes: TestScope[]): Promise {
+ // Route for scope node children (tree structure)
+ await page.route(`**/apis/scope.grafana.app/v0alpha1/namespaces/*/find/scope_node_children*`, async (route) => {
+ const url = new URL(route.request().url());
+ const parentParam = url.searchParams.get('parent');
+ const queryParam = url.searchParams.get('query');
+
+ // Find the appropriate scopes based on parent
+ let scopesToReturn = scopes;
+ if (parentParam) {
+ // Find nested scopes based on parent name
+ const findChildren = (items: TestScope[]): TestScope[] => {
+ for (const item of items) {
+ if (item.name === parentParam && item.children) {
+ return item.children;
+ }
+ if (item.children) {
+ const found = findChildren(item.children);
+ if (found.length > 0) {
+ return found;
+ }
+ }
+ }
+ return [];
+ };
+ scopesToReturn = findChildren(scopes);
+ if (scopesToReturn.length === 0) {
+ scopesToReturn = scopes; // Fallback to root scopes
+ }
+ }
+
+ // Filter by search query if provided
+ if (queryParam) {
+ const query = queryParam.toLowerCase();
+ const filterByQuery = (items: TestScope[]): TestScope[] => {
+ const results: TestScope[] = [];
+ for (const item of items) {
+ // Exact match on name or title containing the query
+ if (item.name.toLowerCase() === query || item.title.toLowerCase() === query) {
+ results.push(item);
+ } else if (item.name.toLowerCase().includes(query) || item.title.toLowerCase().includes(query)) {
+ results.push(item);
+ }
+ // Also search in children
+ if (item.children) {
+ results.push(...filterByQuery(item.children));
+ }
+ }
+ return results;
+ };
+ scopesToReturn = filterByQuery(scopesToReturn);
+ }
+
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ apiVersion: 'scope.grafana.app/v0alpha1',
+ kind: 'FindScopeNodeChildrenResults',
+ metadata: {},
+ items: scopesToReturn.map((scope) => ({
+ kind: 'ScopeNode',
+ apiVersion: 'scope.grafana.app/v0alpha1',
+ metadata: {
+ name: scope.name,
+ namespace: 'default',
+ },
+ spec: {
+ title: scope.title,
+ description: scope.title,
+ disableMultiSelect: scope.disableMultiSelect ?? false,
+ nodeType: scope.children ? 'container' : 'leaf',
+ ...(parentParam && { parentName: parentParam }),
+ ...((scope.addLinks || scope.children) && {
+ linkType: 'scope',
+ linkId: `scope-${scope.name}`,
+ }),
+ ...(scope.redirectPath && { redirectPath: scope.redirectPath }),
+ },
+ })),
+ }),
+ });
+ });
+
+ // Route for individual scope fetching
+ await page.route(`**/apis/scope.grafana.app/v0alpha1/namespaces/*/scopes/*`, async (route) => {
+ const url = route.request().url();
+ const scopeName = url.split('/scopes/')[1]?.split('?')[0];
+
+ // Find the scope in the test data
+ const findScope = (items: TestScope[]): TestScope | undefined => {
+ for (const item of items) {
+ if (`scope-${item.name}` === scopeName) {
+ return item;
+ }
+ if (item.children) {
+ const found = findScope(item.children);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return undefined;
+ };
+
+ const scope = findScope(scopes);
+
+ if (scope) {
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({
+ kind: 'Scope',
+ apiVersion: 'scope.grafana.app/v0alpha1',
+ metadata: {
+ name: `scope-${scope.name}`,
+ namespace: 'default',
+ },
+ spec: {
+ title: scope.title,
+ description: '',
+ filters: scope.filters,
+ category: scope.category,
+ type: scope.type,
+ },
+ }),
+ });
+ } else {
+ await route.fulfill({ status: 404 });
+ }
+ });
+
+ // Note: Dashboard bindings and navigations routes are set up dynamically in applyScopes()
+ // with scope-specific URL patterns to avoid cache issues. They are not set up here.
+}
export type TestScope = {
name: string;
@@ -24,6 +167,9 @@ export type TestScope = {
type ScopeDashboardBinding = Resource;
+/**
+ * Sets up a route for scope node children requests and waits for the response.
+ */
export async function scopeNodeChildrenRequest(
page: Page,
scopes: TestScope[],
@@ -68,10 +214,13 @@ export async function scopeNodeChildrenRequest(
return page.waitForResponse((response) => response.url().includes(`/find/scope_node_children`));
}
+/**
+ * Opens the scopes selector dropdown and waits for the tree to load.
+ */
export async function openScopesSelector(page: Page, scopes?: TestScope[]) {
const click = async () => await page.getByTestId('scopes-selector-input').click();
- if (!scopes) {
+ if (!scopes || USE_LIVE_DATA) {
await click();
return;
}
@@ -82,10 +231,13 @@ export async function openScopesSelector(page: Page, scopes?: TestScope[]) {
await responsePromise;
}
+/**
+ * Expands a scope tree node and waits for children to load.
+ */
export async function expandScopesSelection(page: Page, parentScope: string, scopes?: TestScope[]) {
const click = async () => await page.getByTestId(`scopes-tree-${parentScope}-expand`).click();
- if (!scopes) {
+ if (!scopes || USE_LIVE_DATA) {
await click();
return;
}
@@ -96,6 +248,9 @@ export async function expandScopesSelection(page: Page, parentScope: string, sco
await responsePromise;
}
+/**
+ * Sets up a route for individual scope requests and waits for the response.
+ */
export async function scopeSelectRequest(page: Page, selectedScope: TestScope): Promise {
await page.route(
`**/apis/scope.grafana.app/v0alpha1/namespaces/*/scopes/scope-${selectedScope.name}`,
@@ -125,6 +280,9 @@ export async function scopeSelectRequest(page: Page, selectedScope: TestScope):
return page.waitForResponse((response) => response.url().includes(`/scopes/scope-${selectedScope.name}`));
}
+/**
+ * Selects a scope in the tree.
+ */
export async function selectScope(page: Page, scopeName: string, selectedScope?: TestScope) {
const click = async () => {
const element = page.locator(
@@ -134,7 +292,7 @@ export async function selectScope(page: Page, scopeName: string, selectedScope?:
await element.click({ force: true });
};
- if (!selectedScope) {
+ if (!selectedScope || USE_LIVE_DATA) {
await click();
return;
}
@@ -145,14 +303,22 @@ export async function selectScope(page: Page, scopeName: string, selectedScope?:
await responsePromise;
}
+/**
+ * Applies the selected scopes and waits for the selector to close and page to settle.
+ * Sets up routes dynamically with scope-specific URL patterns to avoid cache issues.
+ */
export async function applyScopes(page: Page, scopes?: TestScope[]) {
const click = async () => {
await page.getByTestId('scopes-selector-apply').scrollIntoViewIfNeeded();
await page.getByTestId('scopes-selector-apply').click({ force: true });
};
- if (!scopes) {
+ if (!scopes || USE_LIVE_DATA) {
await click();
+ // Wait for the apply button to disappear (selector closed)
+ await page.waitForSelector('[data-testid="scopes-selector-apply"]', { state: 'hidden', timeout: 5000 });
+ // Wait for any resulting API calls (dashboard bindings, etc.) to complete
+ await page.waitForLoadState('networkidle');
return;
}
@@ -166,7 +332,7 @@ export async function applyScopes(page: Page, scopes?: TestScope[]) {
const groups: string[] = ['Most relevant', 'Dashboards', 'Something else', ''];
- // Mock scope_dashboard_bindings endpoint
+ // Mock scope_dashboard_bindings endpoint with scope-specific URL pattern
await page.route(dashboardBindingsUrl, async (route) => {
await route.fulfill({
status: 200,
@@ -220,7 +386,7 @@ export async function applyScopes(page: Page, scopes?: TestScope[]) {
});
});
- // Mock scope_navigations endpoint
+ // Mock scope_navigations endpoint with scope-specific URL pattern
await page.route(scopeNavigationsUrl, async (route) => {
await route.fulfill({
status: 200,
@@ -266,21 +432,23 @@ export async function applyScopes(page: Page, scopes?: TestScope[]) {
(response) =>
response.url().includes(`/find/scope_dashboard_bindings`) || response.url().includes(`/find/scope_navigations`)
);
- const scopeRequestPromises: Array> = [];
-
- for (const scope of scopes) {
- scopeRequestPromises.push(scopeSelectRequest(page, scope));
- }
await click();
await responsePromise;
- await Promise.all(scopeRequestPromises);
+ // Wait for the apply button to disappear (selector closed)
+ await page.waitForSelector('[data-testid="scopes-selector-apply"]', { state: 'hidden', timeout: 5000 });
+ // Wait for any resulting API calls to complete
+ await page.waitForLoadState('networkidle');
}
-export async function searchScopes(page: Page, value: string, resultScopes: TestScope[]) {
+/**
+ * Searches for scopes in the tree and waits for results.
+ * Sets up a route dynamically with filtered results to return only matching scopes.
+ */
+export async function searchScopes(page: Page, value: string, resultScopes?: TestScope[]) {
const click = async () => await page.getByTestId('scopes-tree-search').fill(value);
- if (!resultScopes) {
+ if (!resultScopes || USE_LIVE_DATA) {
await click();
return;
}
diff --git a/e2e/dashboards-search-suite/mode0.ini b/e2e/dashboards-search-suite/mode0.ini
index 7248a2f81a2..2b38cd62c9b 100644
--- a/e2e/dashboards-search-suite/mode0.ini
+++ b/e2e/dashboards-search-suite/mode0.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/e2e/dashboards-search-suite/mode1.ini b/e2e/dashboards-search-suite/mode1.ini
index 9875afbec80..b2e9da27c3d 100644
--- a/e2e/dashboards-search-suite/mode1.ini
+++ b/e2e/dashboards-search-suite/mode1.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/e2e/dashboards-search-suite/mode2-legacy-search-api.ini b/e2e/dashboards-search-suite/mode2-legacy-search-api.ini
index 18bd29127a0..9517e105306 100644
--- a/e2e/dashboards-search-suite/mode2-legacy-search-api.ini
+++ b/e2e/dashboards-search-suite/mode2-legacy-search-api.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = false
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/e2e/dashboards-search-suite/mode2.ini b/e2e/dashboards-search-suite/mode2.ini
index d255663299e..138e4960cef 100644
--- a/e2e/dashboards-search-suite/mode2.ini
+++ b/e2e/dashboards-search-suite/mode2.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/e2e/dashboards-search-suite/mode3.ini b/e2e/dashboards-search-suite/mode3.ini
index dfc75a310b0..0835dcb3aaa 100644
--- a/e2e/dashboards-search-suite/mode3.ini
+++ b/e2e/dashboards-search-suite/mode3.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/e2e/dashboards-search-suite/mode4.ini b/e2e/dashboards-search-suite/mode4.ini
index a73a0502353..675cc237298 100644
--- a/e2e/dashboards-search-suite/mode4.ini
+++ b/e2e/dashboards-search-suite/mode4.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/e2e/dashboards-search-suite/mode5.ini b/e2e/dashboards-search-suite/mode5.ini
index a79ab97e792..78b43ee681e 100644
--- a/e2e/dashboards-search-suite/mode5.ini
+++ b/e2e/dashboards-search-suite/mode5.ini
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index c92d2939837..25d3225375e 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -1156,11 +1156,6 @@
"count": 2
}
},
- "public/app/core/config.ts": {
- "no-barrel-files/no-barrel-files": {
- "count": 2
- }
- },
"public/app/core/navigation/types.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
@@ -1342,6 +1337,11 @@
"count": 2
}
},
+ "public/app/features/alerting/unified/api/onCallApi.test.ts": {
+ "no-restricted-syntax": {
+ "count": 2
+ }
+ },
"public/app/features/alerting/unified/components/AnnotationDetailsField.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -1382,6 +1382,11 @@
"count": 1
}
},
+ "public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/alerting/unified/components/import-to-gma/NamespaceAndGroupFilter.tsx": {
"no-restricted-syntax": {
"count": 2
@@ -1622,11 +1627,31 @@
"count": 1
}
},
+ "public/app/features/alerting/unified/mocks/server/configure.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
+ "public/app/features/alerting/unified/mocks/server/handlers/plugins.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
+ "public/app/features/alerting/unified/rule-editor/clone.utils.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 2
+ }
+ },
"public/app/features/alerting/unified/rule-editor/formDefaults.ts": {
"no-restricted-syntax": {
"count": 6
}
},
+ "public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/alerting/unified/types/alerting.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 5
@@ -1637,6 +1662,16 @@
"count": 1
}
},
+ "public/app/features/alerting/unified/utils/config.test.ts": {
+ "no-restricted-syntax": {
+ "count": 6
+ }
+ },
+ "public/app/features/alerting/unified/utils/config.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/alerting/unified/utils/datasource.ts": {
"no-restricted-syntax": {
"count": 2
@@ -1668,12 +1703,20 @@
"count": 1
}
},
+ "public/app/features/alerting/unified/utils/rules.test.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/alerting/unified/utils/rules.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 3
},
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "no-restricted-syntax": {
+ "count": 1
}
},
"public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx": {
@@ -1729,6 +1772,16 @@
"count": 1
}
},
+ "public/app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 2
+ }
+ },
+ "public/app/features/connections/components/AdvisorRedirectNotice/AdvisorRedirectNotice.tsx": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/connections/tabs/ConnectData/ConnectData.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -2068,6 +2121,11 @@
"count": 1
}
},
+ "public/app/features/dashboard/components/GenAI/utils.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx": {
"no-restricted-syntax": {
"count": 3
@@ -2894,6 +2952,71 @@
"count": 1
}
},
+ "public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts": {
+ "no-restricted-syntax": {
+ "count": 6
+ }
+ },
+ "public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.test.ts": {
+ "no-restricted-syntax": {
+ "count": 6
+ }
+ },
+ "public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts": {
+ "no-restricted-syntax": {
+ "count": 6
+ }
+ },
+ "public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.test.ts": {
+ "no-restricted-syntax": {
+ "count": 6
+ }
+ },
+ "public/app/features/plugins/extensions/usePluginComponent.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 3
+ }
+ },
+ "public/app/features/plugins/extensions/usePluginComponents.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 2
+ }
+ },
+ "public/app/features/plugins/extensions/usePluginFunctions.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 2
+ }
+ },
+ "public/app/features/plugins/extensions/usePluginLinks.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 2
+ }
+ },
+ "public/app/features/plugins/extensions/utils.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 27
+ }
+ },
+ "public/app/features/plugins/extensions/utils.tsx": {
+ "no-restricted-syntax": {
+ "count": 7
+ }
+ },
+ "public/app/features/plugins/extensions/validators.test.tsx": {
+ "no-restricted-syntax": {
+ "count": 30
+ }
+ },
+ "public/app/features/plugins/extensions/validators.ts": {
+ "no-restricted-syntax": {
+ "count": 4
+ }
+ },
+ "public/app/features/plugins/sandbox/codeLoader.ts": {
+ "no-restricted-syntax": {
+ "count": 1
+ }
+ },
"public/app/features/plugins/sandbox/distortions.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -3620,46 +3743,21 @@
"count": 1
}
},
- "public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx": {
- "@typescript-eslint/consistent-type-assertions": {
- "count": 1
- }
- },
- "public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx": {
- "@typescript-eslint/consistent-type-assertions": {
- "count": 1
- }
- },
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
- "public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts": {
- "@typescript-eslint/consistent-type-assertions": {
- "count": 1
- }
- },
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
- "public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx": {
- "@typescript-eslint/consistent-type-assertions": {
- "count": 2
- }
- },
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
- "public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts": {
- "@typescript-eslint/consistent-type-assertions": {
- "count": 1
- }
- },
"public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": {
"no-restricted-syntax": {
"count": 1
@@ -4020,11 +4118,6 @@
"count": 1
}
},
- "public/app/plugins/datasource/parca/webpack.config.ts": {
- "no-barrel-files/no-barrel-files": {
- "count": 1
- }
- },
"public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx": {
"no-restricted-syntax": {
"count": 1
diff --git a/eslint.config.js b/eslint.config.js
index bd1be26465a..479f11aac66 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -117,6 +117,8 @@ module.exports = [
'scripts/grafana-server/tmp',
'packages/grafana-ui/src/graveyard', // deprecated UI components slated for removal
'public/build-swagger', // swagger build output
+ 'apps/plugins/plugin/src/generated/meta/v0alpha1',
+ 'apps/plugins/plugin/src/generated/plugin/v0alpha1',
],
},
...grafanaConfig,
@@ -575,6 +577,42 @@ module.exports = [
"Property[key.name='a11y'][value.type='ObjectExpression'] Property[key.name='test'][value.value='off']",
message: 'Skipping a11y tests is not allowed. Please fix the component or story instead.',
},
+ {
+ selector: 'MemberExpression[object.name="config"][property.name="apps"]',
+ message:
+ 'Usage of config.apps is not allowed. Use the function getAppPluginMetas or useAppPluginMetas from @grafana/runtime instead',
+ },
+ ],
+ },
+ },
+ {
+ files: [...commonTestIgnores],
+ ignores: [
+ // FIXME: Remove once all enterprise issues are fixed -
+ // we don't have a suppressions file/approach for enterprise code yet
+ ...enterpriseIgnores,
+ ],
+ rules: {
+ 'no-restricted-syntax': [
+ 'error',
+ {
+ selector: 'MemberExpression[object.name="config"][property.name="apps"]',
+ message:
+ 'Usage of config.apps is not allowed. Use the function getAppPluginMetas or useAppPluginMetas from @grafana/runtime instead',
+ },
+ ],
+ },
+ },
+ {
+ files: [...enterpriseIgnores],
+ rules: {
+ 'no-restricted-syntax': [
+ 'error',
+ {
+ selector: 'MemberExpression[object.name="config"][property.name="apps"]',
+ message:
+ 'Usage of config.apps is not allowed. Use the function getAppPluginMetas or useAppPluginMetas from @grafana/runtime instead',
+ },
],
},
},
@@ -585,6 +623,8 @@ module.exports = [
// FIXME: Remove once all enterprise issues are fixed -
// we don't have a suppressions file/approach for enterprise code yet
...enterpriseIgnores,
+ // Ignore decoupled plugin webpack configs
+ 'public/app/**/webpack.config.ts',
],
rules: {
'no-barrel-files/no-barrel-files': 'error',
diff --git a/go.mod b/go.mod
index 102310173d3..ade26f2e7d1 100644
--- a/go.mod
+++ b/go.mod
@@ -32,20 +32,20 @@ require (
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.40.0 // @grafana/aws-datasources
- github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // @grafana/grafana-operator-experience-squad
+ github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect; @grafana/grafana-operator-experience-squad
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
github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 // @grafana/grafana-operator-experience-squad
- github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // @grafana/grafana-operator-experience-squad
+ github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect; @grafana/grafana-operator-experience-squad
github.com/aws/smithy-go v1.23.2 // @grafana/aws-datasources
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad
- github.com/blevesearch/bleve/v2 v2.5.0 // @grafana/grafana-search-and-storage
- github.com/blevesearch/bleve_index_api v1.2.7 // @grafana/grafana-search-and-storage
+ github.com/blevesearch/bleve/v2 v2.5.7 // @grafana/grafana-search-and-storage
+ github.com/blevesearch/bleve_index_api v1.3.0 // @grafana/grafana-search-and-storage
github.com/blugelabs/bluge v0.2.2 // @grafana/grafana-backend-group
github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // @grafana/grafana-backend-group
@@ -82,14 +82,14 @@ require (
github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group
github.com/golang/snappy v1.0.0 // @grafana/alerting-backend
github.com/google/go-cmp v0.7.0 // @grafana/grafana-backend-group
- github.com/google/go-github/v70 v70.0.0 // indirect; @grafana/grafana-git-ui-sync-team
+ github.com/google/go-github/v70 v70.0.0 // @grafana/grafana-git-ui-sync-team
github.com/google/go-querystring v1.1.0 // indirect; @grafana/oss-big-tent
github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group
github.com/google/wire v0.7.0 // @grafana/grafana-backend-group
github.com/googleapis/gax-go/v2 v2.15.0 // @grafana/grafana-backend-group
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
- github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // @grafana/alerting-backend
+ github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // @grafana/identity-access-team
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
@@ -113,6 +113,7 @@ require (
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.20251118081820-ace37f973a0f // @grafana/observability-traces-and-profiling
+ github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/grafana-search-and-storage
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // @grafana/plugins-platform-backend
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // @grafana/grafana-backend-group
@@ -260,12 +261,13 @@ require (
github.com/grafana/grafana/pkg/aggregator v0.0.0 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana/pkg/apimachinery v0.0.0 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana/pkg/apiserver v0.0.0 // @grafana/grafana-app-platform-squad
+ github.com/grafana/grafana/pkg/plugins v0.0.0 // @grafana/plugins-platform-backend
// This needs to be here for other projects that import grafana/grafana
// For local development grafana/grafana will always use the local files
// Check go.work file for details
github.com/grafana/grafana/pkg/promlib v0.0.8 // @grafana/oss-big-tent
- github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad
+ github.com/grafana/grafana/pkg/semconv v0.0.0 // @grafana/grafana-app-platform-squad
)
// Replace the workspace versions
@@ -294,6 +296,8 @@ replace (
github.com/grafana/grafana/pkg/aggregator => ./pkg/aggregator
github.com/grafana/grafana/pkg/apimachinery => ./pkg/apimachinery
github.com/grafana/grafana/pkg/apiserver => ./pkg/apiserver
+ github.com/grafana/grafana/pkg/plugins => ./pkg/plugins
+ github.com/grafana/grafana/pkg/semconv => ./pkg/semconv
)
require (
@@ -361,22 +365,22 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
- github.com/blevesearch/geo v0.1.20 // indirect
- github.com/blevesearch/go-faiss v1.0.25 // indirect
+ github.com/blevesearch/geo v0.2.4 // indirect
+ github.com/blevesearch/go-faiss v1.0.26 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
- github.com/blevesearch/scorch_segment_api/v2 v2.3.9 // indirect
+ github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
- github.com/blevesearch/zapx/v11 v11.4.1 // indirect
- github.com/blevesearch/zapx/v12 v12.4.1 // indirect
- github.com/blevesearch/zapx/v13 v13.4.1 // indirect
- github.com/blevesearch/zapx/v14 v14.4.1 // indirect
- github.com/blevesearch/zapx/v15 v15.4.1 // indirect
- github.com/blevesearch/zapx/v16 v16.2.2 // indirect
+ github.com/blevesearch/zapx/v11 v11.4.2 // indirect
+ github.com/blevesearch/zapx/v12 v12.4.2 // indirect
+ github.com/blevesearch/zapx/v13 v13.4.2 // indirect
+ github.com/blevesearch/zapx/v14 v14.4.2 // indirect
+ github.com/blevesearch/zapx/v15 v15.4.2 // indirect
+ github.com/blevesearch/zapx/v16 v16.2.8 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/blugelabs/ice v1.0.0 // indirect
github.com/blugelabs/ice/v2 v2.0.1 // indirect
@@ -439,7 +443,6 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
- github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.1 // indirect
@@ -652,11 +655,12 @@ require (
sigs.k8s.io/yaml v1.6.0 // indirect
)
-require github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling
-
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/IBM/pgxpoolprometheus v1.1.2 // indirect
+ github.com/Machiel/slugify v1.0.1 // indirect
+ github.com/ProtonMail/go-crypto v1.3.0 // indirect
+ github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
@@ -676,6 +680,8 @@ require (
github.com/google/gnostic v0.7.1 // indirect
github.com/gophercloud/gophercloud/v2 v2.9.0 // indirect
github.com/grafana/sqlds/v5 v5.0.3 // indirect
+ github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 // indirect
+ github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/moby/go-archive v0.1.0 // indirect
@@ -697,7 +703,7 @@ require (
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240917091248-ae3bbdad8a56
// Use our fork of the upstream Alertmanager.
-replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
+replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f
exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
diff --git a/go.sum b/go.sum
index 0a828108d08..f997af7c68e 100644
--- a/go.sum
+++ b/go.sum
@@ -680,6 +680,7 @@ github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7Og
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
@@ -737,6 +738,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY
github.com/IBM/pgxpoolprometheus v1.1.2 h1:sHJwxoL5Lw4R79Zt+H4Uj1zZ4iqXJLdk7XDE7TPs97U=
github.com/IBM/pgxpoolprometheus v1.1.2/go.mod h1:+vWzISN6S9ssgurhUNmm6AlXL9XLah3TdWJktquKTR8=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
+github.com/Machiel/slugify v1.0.1 h1:EfWSlRWstMadsgzmiV7d0yVd2IFlagWH68Q+DcYCm4E=
+github.com/Machiel/slugify v1.0.1/go.mod h1:fTFGn5uWEynW4CUMG7sWkYXOf1UgDxyTM3DbR6Qfg3k=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
@@ -759,6 +762,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
+github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
+github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -926,14 +931,14 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
-github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4=
-github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk=
-github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY=
-github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
-github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
-github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
-github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
-github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
+github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
+github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
+github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E=
+github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
+github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
+github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
+github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
+github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
@@ -942,8 +947,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+
github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
-github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o=
-github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4=
+github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
+github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
@@ -955,18 +960,18 @@ github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+
github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE=
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
-github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
-github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
-github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
-github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
-github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
-github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
-github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
-github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
-github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
-github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
-github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk=
-github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk=
+github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
+github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
+github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
+github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
+github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
+github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
+github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
+github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
+github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
+github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
+github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
+github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
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=
@@ -1026,6 +1031,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
+github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -1435,8 +1442,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
-github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
@@ -1620,8 +1625,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
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-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
-github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
+github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f h1:3bXOyht68qkfvD6Y8z8XoenFbytSSOIkr/s+AqRzj0o=
+github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f/go.mod h1:Ji0SfJChcwjgq8ljy6Y5CcYfHfAYKXjKYeysOoDS/6s=
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg=
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY=
@@ -1664,8 +1669,6 @@ github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 h1:rDP
github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2/go.mod h1:M7bV60iRB61y0ISPG1HX/oNLZtlh0ZF22rUYwNkAKjo=
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao=
-github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s=
-github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:2HRzUK/xQEYc+8d5If/XSusMcaYq9IptnBSHACiQcOQ=
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 h1:NznuPwItog+rwdVg8hAuGKP29ndRSzJAwhxKldkP8oQ=
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32/go.mod h1:796sq+UcONnSlzA3RtlBZ+b/hrerkZXiEmO8oMjyRwY=
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0=
@@ -1676,8 +1679,8 @@ github.com/grafana/nanogit v0.3.0 h1:XNEef+4Vi+465ZITJs/g/xgnDRJbWhhJ7iQrAnWZ0oQ
github.com/grafana/nanogit v0.3.0/go.mod h1:6s6CCTpyMOHPpcUZaLGI+rgBEKdmxVbhqSGgCK13j7Y=
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=
-github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU=
+github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f h1:9tRhudagkQO2s61SLFLSziIdCm7XlkfypVKDxpcHokg=
+github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f/go.mod h1:AsVdCBeDFN9QbgpJg+8voDAcgsW0RmNvBd70ecMMdC0=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grafana/pyroscope/api v1.2.1-0.20251118081820-ace37f973a0f h1:fTlIj5n4x5dU63XHItug7GLjtnaeJdPqBlqg4zlABq0=
@@ -1753,6 +1756,8 @@ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5O
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
+github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 h1:gCNiM4T5xEc4IpT8vM50CIO+AtElr5kO9l2Rxbq+Sz8=
+github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2/go.mod h1:6ZM4ZdwClyAsiU2uDBmRHCvq0If/03BMbF9U+U7G5pA=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@@ -1877,6 +1882,10 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
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/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4LFvNlPz2nBKd3OMlGKIQ69OmR4=
+github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg=
+github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY=
+github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
diff --git a/go.work b/go.work
index 208eca0454a..462bbb36a04 100644
--- a/go.work
+++ b/go.work
@@ -38,6 +38,6 @@ use (
./pkg/semconv
)
-replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
+replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240917091248-ae3bbdad8a56
diff --git a/go.work.sum b/go.work.sum
index 0300e5becbc..6388285d2bf 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -259,6 +259,7 @@ codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3
codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw=
codeberg.org/go-pdf/fpdf v0.10.0 h1:u+w669foDDx5Ds43mpiiayp40Ov6sZalgcPMDBcZRd4=
codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU=
+connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM=
contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
@@ -280,7 +281,6 @@ github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fw
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-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
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=
@@ -520,14 +520,40 @@ github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC
github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
+github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
+github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
+github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
+github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
+github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
+github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E=
+github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
+github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
+github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
+github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA=
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
github.com/blevesearch/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI=
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
+github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8=
+github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
+github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLsL+B5A=
github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc=
github.com/blevesearch/stempel v0.2.0/go.mod h1:wjeTHqQv+nQdbPuJ/YcvOjTInA2EIc6Ks1FoSUzSLvc=
+github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
+github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
+github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
+github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
+github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
+github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
+github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
+github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
+github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
+github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
+github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
+github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
+github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
@@ -906,6 +932,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250729175202-b4b881b7b263/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM=
+github.com/grafana/alerting v0.0.0-20260112110054-6c6f13659ad3 h1:KVncUdAc5YwY/OQmw6HgzJmbRKn6IwrhvtcBAd1yDHo=
+github.com/grafana/alerting v0.0.0-20260112110054-6c6f13659ad3/go.mod h1:Oy4MthJqfErlieO14ryZXdukDrUACy8Lg56P3zP7S1k=
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
@@ -997,6 +1025,7 @@ github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975
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/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae/go.mod h1:6CJ1uXmLZ13ufpO9xE4pST+DyaBt0uszzrV0YnoaVLQ=
github.com/grafana/sqlds/v4 v4.2.4/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU=
github.com/grafana/sqlds/v4 v4.2.7/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU=
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU=
@@ -1087,6 +1116,7 @@ github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU=
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
+github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jsternberg/zap-logfmt v1.3.0 h1:z1n1AOHVVydOOVuyphbOKyR4NICDQFiJMn1IK5hVQ5Y=
github.com/jsternberg/zap-logfmt v1.3.0/go.mod h1:N3DENp9WNmCZxvkBD/eReWwz1149BK6jEN9cQ4fNwZE=
@@ -1911,6 +1941,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
diff --git a/jest.config.js b/jest.config.js
index 17a2ce9ca32..f9d431cf5d3 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -82,6 +82,7 @@ module.exports = {
// Decoupled plugins run their own tests so ignoring them here.
'/public/app/plugins/datasource/azuremonitor',
'/public/app/plugins/datasource/cloud-monitoring',
+ '/public/app/plugins/datasource/elasticsearch',
'/public/app/plugins/datasource/grafana-postgresql-datasource',
'/public/app/plugins/datasource/grafana-pyroscope-datasource',
'/public/app/plugins/datasource/grafana-testdata-datasource',
diff --git a/package.json b/package.json
index 72a8f638cb6..0a39ff67aea 100644
--- a/package.json
+++ b/package.json
@@ -62,8 +62,7 @@
"stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json",
"storybook": "yarn workspace @grafana/ui storybook --ci",
"storybook:build": "yarn workspace @grafana/ui storybook:build",
- "themes-schema": "typescript-json-schema ./tsconfig.json NewThemeOptions --include 'packages/grafana-data/src/themes/createTheme.ts' --out public/app/features/theme-playground/schema.generated.json",
- "themes-generate": "yarn themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --conditions=@grafana-app/source --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
+ "themes-generate": "yarn workspace @grafana/data themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --conditions=@grafana-app/source --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
"themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'",
"typecheck": "tsc --noEmit && yarn run packages:typecheck",
"plugins:build-bundled": "echo 'bundled plugins are no longer supported'",
@@ -254,7 +253,6 @@
"ts-jest": "29.4.0",
"ts-node": "10.9.2",
"typescript": "5.9.2",
- "typescript-json-schema": "^0.65.1",
"webpack": "5.101.0",
"webpack-assets-manifest": "^5.1.0",
"webpack-cli": "6.0.1",
@@ -265,7 +263,7 @@
"webpackbar": "^7.0.0",
"yaml": "^2.0.0",
"yargs": "^18.0.0",
- "zod": "^4.0.0"
+ "zod": "^4.3.0"
},
"dependencies": {
"@bsull/augurs": "^0.10.0",
@@ -295,8 +293,8 @@
"@grafana/plugin-ui": "^0.11.1",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
- "@grafana/scenes": "v6.52.1",
- "@grafana/scenes-react": "v6.52.1",
+ "@grafana/scenes": "6.52.2",
+ "@grafana/scenes-react": "6.52.2",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
diff --git a/packages/grafana-api-clients/src/clients/rtkq/createBaseQuery.ts b/packages/grafana-api-clients/src/clients/rtkq/createBaseQuery.ts
index 477e139faac..c6216cc13b5 100644
--- a/packages/grafana-api-clients/src/clients/rtkq/createBaseQuery.ts
+++ b/packages/grafana-api-clients/src/clients/rtkq/createBaseQuery.ts
@@ -34,6 +34,8 @@ export function createBaseQuery({ baseURL }: CreateBaseQueryOptions): BaseQueryF
getBackendSrv().fetch({
...requestOptions,
url: baseURL + requestOptions.url,
+ // Default to GET so backend_srv correctly skips success alerts for queries
+ method: requestOptions.method ?? 'GET',
showErrorAlert: requestOptions.showErrorAlert ?? false,
data: requestOptions.body,
headers,
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 326b53ccedd..9605b8e9355 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
@@ -246,6 +246,8 @@ const injectedRtkApi = api
facetLimit: queryArg.facetLimit,
tags: queryArg.tags,
libraryPanel: queryArg.libraryPanel,
+ panelType: queryArg.panelType,
+ dataSourceType: queryArg.dataSourceType,
permission: queryArg.permission,
sort: queryArg.sort,
limit: queryArg.limit,
@@ -674,6 +676,10 @@ export type SearchDashboardsAndFoldersApiArg = {
tags?: string[];
/** find dashboards that reference a given libraryPanel */
libraryPanel?: string;
+ /** find dashboards using panels of a given plugin type */
+ panelType?: string;
+ /** find dashboards using datasources of a given plugin type */
+ dataSourceType?: string;
/** permission needed for the resource (view, edit, admin) */
permission?: 'view' | 'edit' | 'admin';
/** sortable field */
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 4ceed793cad..0fbbb9cddc9 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
@@ -727,17 +727,6 @@ const injectedRtkApi = api
}),
invalidatesTags: ['dashboards', 'permissions'],
}),
- restoreDashboardVersionByUid: build.mutation<
- RestoreDashboardVersionByUidApiResponse,
- RestoreDashboardVersionByUidApiArg
- >({
- query: (queryArg) => ({
- url: `/dashboards/uid/${queryArg.uid}/restore`,
- method: 'POST',
- body: queryArg.restoreDashboardVersionCommand,
- }),
- invalidatesTags: ['dashboards', 'versions'],
- }),
getDashboardVersionsByUid: build.query({
query: (queryArg) => ({
url: `/dashboards/uid/${queryArg.uid}/versions`,
@@ -2628,26 +2617,6 @@ export type UpdateDashboardPermissionsByUidApiArg = {
uid: string;
updateDashboardAclCommand: UpdateDashboardAclCommand;
};
-export type RestoreDashboardVersionByUidApiResponse = /** status 200 (empty) */ {
- /** FolderUID The unique identifier (uid) of the folder the dashboard belongs to. */
- folderUid?: string;
- /** ID The unique identifier (id) of the created/updated dashboard. */
- id: number;
- /** Status status of the response. */
- status: string;
- /** Slug The slug of the dashboard. */
- title: string;
- /** UID The unique identifier (uid) of the created/updated dashboard. */
- uid: string;
- /** URL The relative URL for accessing the created/updated dashboard. */
- url: string;
- /** Version The version of the dashboard. */
- version: number;
-};
-export type RestoreDashboardVersionByUidApiArg = {
- uid: string;
- restoreDashboardVersionCommand: RestoreDashboardVersionCommand;
-};
export type GetDashboardVersionsByUidApiResponse = /** status 200 (empty) */ DashboardVersionResponseMeta;
export type GetDashboardVersionsByUidApiArg = {
uid: string;
@@ -4568,9 +4537,6 @@ export type DashboardAclUpdateItem = {
export type UpdateDashboardAclCommand = {
items?: DashboardAclUpdateItem[];
};
-export type RestoreDashboardVersionCommand = {
- version?: number;
-};
export type DashboardVersionMeta = {
created?: string;
createdBy?: string;
@@ -6633,7 +6599,6 @@ export const {
useGetDashboardPermissionsListByUidQuery,
useLazyGetDashboardPermissionsListByUidQuery,
useUpdateDashboardPermissionsByUidMutation,
- useRestoreDashboardVersionByUidMutation,
useGetDashboardVersionsByUidQuery,
useLazyGetDashboardVersionsByUidQuery,
useGetDashboardVersionByUidQuery,
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 b6519295c66..40d4299a1a0 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
@@ -1452,7 +1452,7 @@ export type ConnectionSecure = {
/** PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back */
privateKey?: InlineSecureValue;
/** Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back */
- webhook?: InlineSecureValue;
+ token?: InlineSecureValue;
};
export type BitbucketConnectionConfig = {
/** App client ID */
diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json
index df595973cca..60db9295fb4 100644
--- a/packages/grafana-data/package.json
+++ b/packages/grafana-data/package.json
@@ -35,6 +35,14 @@
},
"./test": {
"@grafana-app/source": "./test/index.ts"
+ },
+ "./themes/schema.generated.json": {
+ "@grafana-app/source": "./src/themes/schema.generated.json",
+ "default": "./dist/esm/themes/schema.generated.json"
+ },
+ "./themes/definitions/*.json": {
+ "@grafana-app/source": "./src/themes/themeDefinitions/*.json",
+ "default": "./dist/esm/themes/themeDefinitions/*.json"
}
},
"publishConfig": {
@@ -47,11 +55,12 @@
"LICENSE_APACHE2"
],
"scripts": {
- "build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
+ "build": "yarn themes-schema && tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./unstable ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js",
- "postpack": "mv package.json.bak package.json"
+ "postpack": "mv package.json.bak package.json",
+ "themes-schema": "tsx ./scripts/generateSchema.ts"
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",
@@ -81,10 +90,12 @@
"tinycolor2": "1.6.0",
"tslib": "2.8.1",
"uplot": "1.6.32",
- "xss": "^1.0.14"
+ "xss": "^1.0.14",
+ "zod": "^4.3.0"
},
"devDependencies": {
"@grafana/scenes": "6.38.0",
+ "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-node-resolve": "16.0.1",
"@testing-library/react": "16.3.0",
"@types/history": "4.7.11",
@@ -99,8 +110,10 @@
"react-dom": "18.3.1",
"rimraf": "6.0.1",
"rollup": "^4.22.4",
+ "rollup-plugin-copy": "3.5.0",
"rollup-plugin-esbuild": "6.2.1",
"rollup-plugin-node-externals": "^8.0.0",
+ "tsx": "^4.21.0",
"typescript": "5.9.2"
},
"peerDependencies": {
diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts
index 87008ddc45f..50af331c37c 100644
--- a/packages/grafana-data/rollup.config.ts
+++ b/packages/grafana-data/rollup.config.ts
@@ -1,20 +1,40 @@
+import json from '@rollup/plugin-json';
import { createRequire } from 'node:module';
+import copy from 'rollup-plugin-copy';
import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts';
const rq = createRequire(import.meta.url);
const pkg = rq('./package.json');
+const grafanaDataPlugins = [
+ ...plugins,
+ copy({
+ targets: [
+ {
+ src: 'src/themes/schema.generated.json',
+ dest: 'dist/esm/',
+ },
+ {
+ src: 'src/themes/themeDefinitions/*.json',
+ dest: 'dist/esm/',
+ },
+ ],
+ flatten: false,
+ }),
+ json(),
+];
+
export default [
{
input: entryPoint,
- plugins,
+ plugins: grafanaDataPlugins,
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
{
input: 'src/unstable.ts',
- plugins,
+ plugins: grafanaDataPlugins,
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
diff --git a/packages/grafana-data/scripts/generateSchema.ts b/packages/grafana-data/scripts/generateSchema.ts
new file mode 100644
index 00000000000..f461999376e
--- /dev/null
+++ b/packages/grafana-data/scripts/generateSchema.ts
@@ -0,0 +1,22 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { NewThemeOptionsSchema } from '../src/themes/createTheme';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const jsonOut = path.join(__dirname, '..', 'src', 'themes', 'schema.generated.json');
+
+fs.writeFileSync(
+ jsonOut,
+ JSON.stringify(
+ NewThemeOptionsSchema.toJSONSchema({
+ target: 'draft-07',
+ }),
+ undefined,
+ 2
+ )
+);
+
+console.log('Successfully generated theme schema');
diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts
index 767da439543..b0aa271be3f 100644
--- a/packages/grafana-data/src/field/fieldOverrides.test.ts
+++ b/packages/grafana-data/src/field/fieldOverrides.test.ts
@@ -9,6 +9,7 @@ import { FieldColorModeId } from '../types/fieldColor';
import { FieldConfigPropertyItem, FieldConfigSource } from '../types/fieldOverrides';
import { InterpolateFunction } from '../types/panel';
import { ThresholdsMode } from '../types/thresholds';
+import { MappingType } from '../types/valueMapping';
import { Registry } from '../utils/Registry';
import { locationUtil } from '../utils/location';
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
@@ -999,6 +1000,45 @@ describe('setDynamicConfigValue', () => {
expect(config.custom.property3).toEqual({});
expect(config.displayName).toBeUndefined();
});
+
+ it('works correctly with multiple value mappings in the same override', () => {
+ const config: FieldConfig = {
+ mappings: [{ type: MappingType.ValueToText, options: { existing: { text: 'existing' } } }],
+ };
+
+ setDynamicConfigValue(
+ config,
+ {
+ id: 'mappings',
+ value: [{ type: MappingType.ValueToText, options: { first: { text: 'first' } } }],
+ },
+ {
+ fieldConfigRegistry: customFieldRegistry,
+ data: [],
+ field: { type: FieldType.number } as Field,
+ dataFrameIndex: 0,
+ }
+ );
+
+ setDynamicConfigValue(
+ config,
+ {
+ id: 'mappings',
+ value: [{ type: MappingType.ValueToText, options: { second: { text: 'second' } } }],
+ },
+ {
+ fieldConfigRegistry: customFieldRegistry,
+ data: [],
+ field: { type: FieldType.number } as Field,
+ dataFrameIndex: 0,
+ }
+ );
+
+ expect(config.mappings).toHaveLength(3);
+ expect(config.mappings![0]).toEqual({ type: MappingType.ValueToText, options: { existing: { text: 'existing' } } });
+ expect(config.mappings![1]).toEqual({ type: MappingType.ValueToText, options: { first: { text: 'first' } } });
+ expect(config.mappings![2]).toEqual({ type: MappingType.ValueToText, options: { second: { text: 'second' } } });
+ });
});
describe('getLinksSupplier', () => {
diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts
index 8b345036c64..bf56f811106 100644
--- a/packages/grafana-data/src/field/fieldOverrides.ts
+++ b/packages/grafana-data/src/field/fieldOverrides.ts
@@ -341,7 +341,7 @@ export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigV
return;
}
- const val = item.process(value.value, context, item.settings);
+ let val = item.process(value.value, context, item.settings);
const remove = val === undefined || val === null;
@@ -352,6 +352,15 @@ export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigV
unset(config, item.path);
}
} else {
+ // Merge arrays (e.g. mappings) when multiple overrides target the same field
+ if (Array.isArray(val)) {
+ const existingValue = item.isCustom ? get(config.custom, item.path) : get(config, item.path);
+
+ if (Array.isArray(existingValue)) {
+ val = [...existingValue, ...val];
+ }
+ }
+
if (item.isCustom) {
if (!config.custom) {
config.custom = {};
diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts
index 6027b566764..5ed081b00f0 100644
--- a/packages/grafana-data/src/index.ts
+++ b/packages/grafana-data/src/index.ts
@@ -844,7 +844,6 @@ export {
DataLinkConfigOrigin,
SupportedTransformationType,
type InternalDataLink,
- type LinkTarget,
type LinkModel,
type LinkModelSupplier,
VariableOrigin,
@@ -852,6 +851,7 @@ export {
VariableSuggestionsScope,
OneClickMode,
} from './types/dataLink';
+export { type LinkTarget } from './types/linkTarget';
export {
type Action,
type ActionModel,
diff --git a/packages/grafana-data/src/internal/index.ts b/packages/grafana-data/src/internal/index.ts
index e2dab753baa..230cdd2cbf9 100644
--- a/packages/grafana-data/src/internal/index.ts
+++ b/packages/grafana-data/src/internal/index.ts
@@ -93,7 +93,6 @@ export { DataTransformerID } from '../transformations/transformers/ids';
export { mergeTransformer } from '../transformations/transformers/merge';
export { getThemeById } from '../themes/registry';
-export * as experimentalThemeDefinitions from '../themes/themeDefinitions';
export { GrafanaEdition } from '../types/config';
export { SIPrefix } from '../valueFormats/symbolFormatters';
@@ -106,3 +105,4 @@ export { findNumericFieldMinMax } from '../field/fieldOverrides';
export { type PanelOptionsSupplier } from '../panel/PanelPlugin';
export { sanitize, sanitizeUrl } from '../text/sanitize';
export { type NestedValueAccess, type NestedPanelOptions, isNestedPanelOptions } from '../utils/OptionsUIBuilders';
+export { NewThemeOptionsSchema } from '../themes/createTheme';
diff --git a/packages/grafana-data/src/themes/createColors.ts b/packages/grafana-data/src/themes/createColors.ts
index 09b94fd3b3e..ee7beab02d6 100644
--- a/packages/grafana-data/src/themes/createColors.ts
+++ b/packages/grafana-data/src/themes/createColors.ts
@@ -1,83 +1,103 @@
import { merge } from 'lodash';
+import { z } from 'zod';
import { alpha, darken, emphasize, getContrastRatio, lighten } from './colorManipulator';
import { palette } from './palette';
-import { DeepPartial, ThemeRichColor } from './types';
+import { DeepRequired, ThemeRichColor, ThemeRichColorInputSchema } from './types';
+const ThemeColorsModeSchema = z.enum(['light', 'dark']);
/** @internal */
-export type ThemeColorsMode = 'light' | 'dark';
+export type ThemeColorsMode = z.infer;
+const createThemeColorsBaseSchema = (color: TColor) =>
+ z
+ .object({
+ mode: ThemeColorsModeSchema,
+
+ primary: color,
+ secondary: color,
+ info: color,
+ error: color,
+ success: color,
+ warning: color,
+
+ text: z.object({
+ primary: z.string().optional(),
+ secondary: z.string().optional(),
+ disabled: z.string().optional(),
+ link: z.string().optional(),
+ /** Used for auto white or dark text on colored backgrounds */
+ maxContrast: z.string().optional(),
+ }),
+
+ background: z.object({
+ /** Dashboard and body background */
+ canvas: z.string().optional(),
+ /** Primary content pane background (panels etc) */
+ primary: z.string().optional(),
+ /** Cards and elements that need to stand out on the primary background */
+ secondary: z.string().optional(),
+ /**
+ * For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark
+ * themes it has a brighter shade to help give it contrast against the primary background.
+ **/
+ elevated: z.string().optional(),
+ }),
+
+ border: z.object({
+ weak: z.string().optional(),
+ medium: z.string().optional(),
+ strong: z.string().optional(),
+ }),
+
+ gradients: z.object({
+ brandVertical: z.string().optional(),
+ brandHorizontal: z.string().optional(),
+ }),
+
+ action: z.object({
+ /** Used for selected menu item / select option */
+ selected: z.string().optional(),
+ /**
+ * @alpha (Do not use from plugins)
+ * Used for selected items when background only change is not enough (Currently only used for FilterPill)
+ **/
+ selectedBorder: z.string().optional(),
+ /** Used for hovered menu item / select option */
+ hover: z.string().optional(),
+ /** Used for button/colored background hover opacity */
+ hoverOpacity: z.number().optional(),
+ /** Used focused menu item / select option */
+ focus: z.string().optional(),
+ /** Used for disabled buttons and inputs */
+ disabledBackground: z.string().optional(),
+ /** Disabled text */
+ disabledText: z.string().optional(),
+ /** Disablerd opacity */
+ disabledOpacity: z.number().optional(),
+ }),
+
+ hoverFactor: z.number(),
+ contrastThreshold: z.number(),
+ tonalOffset: z.number(),
+ })
+ .partial();
+
+// Need to override the zod type to include the generic properly
/** @internal */
-export interface ThemeColorsBase {
- mode: ThemeColorsMode;
-
+export type ThemeColorsBase = DeepRequired<
+ Omit<
+ z.infer>,
+ 'primary' | 'secondary' | 'info' | 'error' | 'success' | 'warning'
+ >
+> & {
primary: TColor;
secondary: TColor;
info: TColor;
error: TColor;
success: TColor;
warning: TColor;
-
- text: {
- primary: string;
- secondary: string;
- disabled: string;
- link: string;
- /** Used for auto white or dark text on colored backgrounds */
- maxContrast: string;
- };
-
- background: {
- /** Dashboard and body background */
- canvas: string;
- /** Primary content pane background (panels etc) */
- primary: string;
- /** Cards and elements that need to stand out on the primary background */
- secondary: string;
- /**
- * For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark
- * themes it has a brighter shade to help give it contrast against the primary background.
- **/
- elevated: string;
- };
-
- border: {
- weak: string;
- medium: string;
- strong: string;
- };
-
- gradients: {
- brandVertical: string;
- brandHorizontal: string;
- };
-
- action: {
- /** Used for selected menu item / select option */
- selected: string;
- /**
- * @alpha (Do not use from plugins)
- * Used for selected items when background only change is not enough (Currently only used for FilterPill)
- **/
- selectedBorder: string;
- /** Used for hovered menu item / select option */
- hover: string;
- /** Used for button/colored background hover opacity */
- hoverOpacity: number;
- /** Used focused menu item / select option */
- focus: string;
- /** Used for disabled buttons and inputs */
- disabledBackground: string;
- /** Disabled text */
- disabledText: string;
- /** Disablerd opacity */
- disabledOpacity: number;
- };
-
- hoverFactor: number;
- contrastThreshold: number;
- tonalOffset: number;
-}
+};
export interface ThemeHoverStrengh {}
@@ -89,8 +109,10 @@ export interface ThemeColors extends ThemeColorsBase {
emphasize(color: string, amount?: number): string;
}
+export const ThemeColorsInputSchema = createThemeColorsBaseSchema(ThemeRichColorInputSchema);
+
/** @internal */
-export type ThemeColorsInput = DeepPartial>;
+export type ThemeColorsInput = z.infer;
class DarkColors implements ThemeColorsBase> {
mode: ThemeColorsMode = 'dark';
diff --git a/packages/grafana-data/src/themes/createShape.ts b/packages/grafana-data/src/themes/createShape.ts
index 42291fb78d0..f454eda6861 100644
--- a/packages/grafana-data/src/themes/createShape.ts
+++ b/packages/grafana-data/src/themes/createShape.ts
@@ -1,3 +1,5 @@
+import { z } from 'zod';
+
/** @beta */
export interface ThemeShape {
/**
@@ -34,9 +36,12 @@ export interface Radii {
}
/** @internal */
-export interface ThemeShapeInput {
- borderRadius?: number;
-}
+export const ThemeShapeInputSchema = z.object({
+ borderRadius: z.int().nonnegative().optional(),
+});
+
+/** @internal */
+export type ThemeShapeInput = z.infer;
export function createShape(options: ThemeShapeInput): ThemeShape {
const baseBorderRadius = options.borderRadius ?? 6;
diff --git a/packages/grafana-data/src/themes/createSpacing.ts b/packages/grafana-data/src/themes/createSpacing.ts
index 2fa047b3e68..1ba51c61917 100644
--- a/packages/grafana-data/src/themes/createSpacing.ts
+++ b/packages/grafana-data/src/themes/createSpacing.ts
@@ -1,11 +1,15 @@
// Code based on Material UI
// The MIT License (MIT)
// Copyright (c) 2014 Call-Em-All
+import { z } from 'zod';
/** @internal */
-export type ThemeSpacingOptions = {
- gridSize?: number;
-};
+export const ThemeSpacingOptionsSchema = z.object({
+ gridSize: z.int().positive().optional(),
+});
+
+/** @internal */
+export type ThemeSpacingOptions = z.infer;
/** @internal */
export type ThemeSpacingArgument = number | string;
diff --git a/packages/grafana-data/src/themes/createTheme.ts b/packages/grafana-data/src/themes/createTheme.ts
index fd4d8080a4e..a4fa773cf52 100644
--- a/packages/grafana-data/src/themes/createTheme.ts
+++ b/packages/grafana-data/src/themes/createTheme.ts
@@ -1,28 +1,37 @@
+import * as z from 'zod';
+
import { createBreakpoints } from './breakpoints';
-import { createColors, ThemeColorsInput } from './createColors';
+import { createColors, ThemeColorsInputSchema } from './createColors';
import { createComponents } from './createComponents';
import { createShadows } from './createShadows';
-import { createShape, ThemeShapeInput } from './createShape';
-import { createSpacing, ThemeSpacingOptions } from './createSpacing';
+import { createShape, ThemeShapeInputSchema } from './createShape';
+import { createSpacing, ThemeSpacingOptionsSchema } from './createSpacing';
import { createTransitions } from './createTransitions';
-import { createTypography, ThemeTypographyInput } from './createTypography';
+import { createTypography, ThemeTypographyInputSchema } from './createTypography';
import { createV1Theme } from './createV1Theme';
-import { createVisualizationColors, ThemeVisualizationColorsInput } from './createVisualizationColors';
+import { createVisualizationColors, ThemeVisualizationColorsInputSchema } from './createVisualizationColors';
import { GrafanaTheme2 } from './types';
import { zIndex } from './zIndex';
-/** @internal */
-export interface NewThemeOptions {
- name?: string;
- colors?: ThemeColorsInput;
- spacing?: ThemeSpacingOptions;
- shape?: ThemeShapeInput;
- typography?: ThemeTypographyInput;
- visualization?: ThemeVisualizationColorsInput;
-}
+export const NewThemeOptionsSchema = z.object({
+ name: z.string(),
+ id: z.string(),
+ colors: ThemeColorsInputSchema.optional(),
+ spacing: ThemeSpacingOptionsSchema.optional(),
+ shape: ThemeShapeInputSchema.optional(),
+ typography: ThemeTypographyInputSchema.optional(),
+ visualization: ThemeVisualizationColorsInputSchema.optional(),
+});
/** @internal */
-export function createTheme(options: NewThemeOptions = {}): GrafanaTheme2 {
+export type NewThemeOptions = z.infer;
+
+/** @internal */
+export function createTheme(
+ options: Omit & {
+ name?: NewThemeOptions['name'];
+ } = {}
+): GrafanaTheme2 {
const {
name,
colors: colorsInput = {},
diff --git a/packages/grafana-data/src/themes/createTypography.ts b/packages/grafana-data/src/themes/createTypography.ts
index 25c5fa7c91b..3504e52d2fa 100644
--- a/packages/grafana-data/src/themes/createTypography.ts
+++ b/packages/grafana-data/src/themes/createTypography.ts
@@ -1,6 +1,7 @@
// Code based on Material UI
// The MIT License (MIT)
// Copyright (c) 2014 Call-Em-All
+import { z } from 'zod';
import { ThemeColors } from './createColors';
@@ -40,18 +41,20 @@ export interface ThemeTypographyVariant {
letterSpacing?: string;
}
-export interface ThemeTypographyInput {
- fontFamily?: string;
- fontFamilyMonospace?: string;
- fontSize?: number;
- fontWeightLight?: number;
- fontWeightRegular?: number;
- fontWeightMedium?: number;
- fontWeightBold?: number;
- // hat's the font-size on the html element.
+export const ThemeTypographyInputSchema = z.object({
+ fontFamily: z.string().optional(),
+ fontFamilyMonospace: z.string().optional(),
+ fontSize: z.number().positive().optional(),
+ fontWeightLight: z.number().positive().optional(),
+ fontWeightRegular: z.number().positive().optional(),
+ fontWeightMedium: z.number().positive().optional(),
+ fontWeightBold: z.number().positive().optional(),
+ // what's the font-size on the html element.
// 16px is the default font-size used by browsers.
- htmlFontSize?: number;
-}
+ htmlFontSize: z.number().positive().optional(),
+});
+
+export type ThemeTypographyInput = z.infer;
const defaultFontFamily = "'Inter', 'Helvetica', 'Arial', sans-serif";
const defaultFontFamilyMonospace = "'Roboto Mono', monospace";
diff --git a/packages/grafana-data/src/themes/createVisualizationColors.ts b/packages/grafana-data/src/themes/createVisualizationColors.ts
index fca963c9c07..90acbbc2144 100644
--- a/packages/grafana-data/src/themes/createVisualizationColors.ts
+++ b/packages/grafana-data/src/themes/createVisualizationColors.ts
@@ -1,3 +1,5 @@
+import { z } from 'zod';
+
import { FALLBACK_COLOR } from '../types/fieldColor';
import { ThemeColors } from './createColors';
@@ -26,29 +28,44 @@ export interface ThemeVizColor {
type ThemeVizColorName = 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple';
-type ThemeVizColorShadeName =
- | `super-light-${T}`
- | `light-${T}`
- | T
- | `semi-dark-${T}`
- | `dark-${T}`;
+const createShadeSchema = (color: T extends ThemeVizColorName ? T : never) =>
+ z.enum([`super-light-${color}`, `light-${color}`, color, `semi-dark-${color}`, `dark-${color}`]);
-type ThemeVizHueGeneric = T extends ThemeVizColorName
- ? {
- name: T;
- shades: Array>;
- }
- : never;
+type ThemeVizColorShadeName = z.infer>>;
+
+const createHueSchema = (color: T extends ThemeVizColorName ? T : never) =>
+ z.object({
+ name: z.literal(color),
+ shades: z.array(
+ z.object({
+ color: z.string(),
+ name: createShadeSchema(color),
+ aliases: z.array(z.string()).optional(),
+ primary: z.boolean().optional(),
+ })
+ ),
+ });
+
+const ThemeVizHueSchema = z.union([
+ createHueSchema('red'),
+ createHueSchema('orange'),
+ createHueSchema('yellow'),
+ createHueSchema('green'),
+ createHueSchema('blue'),
+ createHueSchema('purple'),
+]);
/**
* @alpha
*/
-export type ThemeVizHue = ThemeVizHueGeneric;
+export type ThemeVizHue = z.infer;
-export type ThemeVisualizationColorsInput = {
- hues?: ThemeVizHue[];
- palette?: string[];
-};
+export const ThemeVisualizationColorsInputSchema = z.object({
+ hues: z.array(ThemeVizHueSchema).optional(),
+ palette: z.array(z.string()).optional(),
+});
+
+export type ThemeVisualizationColorsInput = z.infer;
/**
* @internal
diff --git a/packages/grafana-data/src/themes/registry.ts b/packages/grafana-data/src/themes/registry.ts
index c4cf352b622..cfa4a10c6e4 100644
--- a/packages/grafana-data/src/themes/registry.ts
+++ b/packages/grafana-data/src/themes/registry.ts
@@ -1,7 +1,18 @@
import { Registry, RegistryItem } from '../utils/Registry';
-import { createTheme } from './createTheme';
-import * as extraThemes from './themeDefinitions';
+import { createTheme, NewThemeOptionsSchema } from './createTheme';
+import aubergine from './themeDefinitions/aubergine.json';
+import debug from './themeDefinitions/debug.json';
+import desertbloom from './themeDefinitions/desertbloom.json';
+import gildedgrove from './themeDefinitions/gildedgrove.json';
+import gloom from './themeDefinitions/gloom.json';
+import mars from './themeDefinitions/mars.json';
+import matrix from './themeDefinitions/matrix.json';
+import sapphiredusk from './themeDefinitions/sapphiredusk.json';
+import synthwave from './themeDefinitions/synthwave.json';
+import tron from './themeDefinitions/tron.json';
+import victorian from './themeDefinitions/victorian.json';
+import zen from './themeDefinitions/zen.json';
import { GrafanaTheme2 } from './types';
export interface ThemeRegistryItem extends RegistryItem {
@@ -9,6 +20,21 @@ export interface ThemeRegistryItem extends RegistryItem {
build: () => GrafanaTheme2;
}
+const extraThemes: { [key: string]: unknown } = {
+ aubergine,
+ debug,
+ desertbloom,
+ gildedgrove,
+ gloom,
+ mars,
+ matrix,
+ sapphiredusk,
+ synthwave,
+ tron,
+ victorian,
+ zen,
+};
+
/**
* @internal
* Only for internal use, never use this from a plugin
@@ -42,9 +68,6 @@ export function getBuiltInThemes(allowedExtras: string[]) {
return sortedThemes;
}
-/**
- * There is also a backend list at pkg/services/preference/themes.go
- */
const themeRegistry = new Registry(() => {
return [
{ id: 'system', name: 'System preference', build: getSystemPreferenceTheme },
@@ -53,13 +76,19 @@ const themeRegistry = new Registry(() => {
];
});
-for (const [id, theme] of Object.entries(extraThemes)) {
- themeRegistry.register({
- id,
- name: theme.name ?? '',
- build: () => createTheme(theme),
- isExtra: true,
- });
+for (const [name, json] of Object.entries(extraThemes)) {
+ const result = NewThemeOptionsSchema.safeParse(json);
+ if (!result.success) {
+ console.error(`Invalid theme definition for theme ${name}: ${result.error.message}`);
+ } else {
+ const theme = result.data;
+ themeRegistry.register({
+ id: theme.id,
+ name: theme.name,
+ build: () => createTheme(theme),
+ isExtra: true,
+ });
+ }
}
function getSystemPreferenceTheme() {
diff --git a/packages/grafana-data/src/themes/schema.generated.json b/packages/grafana-data/src/themes/schema.generated.json
new file mode 100644
index 00000000000..366ab9c05d6
--- /dev/null
+++ b/packages/grafana-data/src/themes/schema.generated.json
@@ -0,0 +1,608 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "colors": {
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["light", "dark"]
+ },
+ "primary": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "main": {
+ "type": "string"
+ },
+ "shade": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "border": {
+ "type": "string"
+ },
+ "transparent": {
+ "type": "string"
+ },
+ "borderTransparent": {
+ "type": "string"
+ },
+ "contrastText": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "secondary": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "main": {
+ "type": "string"
+ },
+ "shade": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "border": {
+ "type": "string"
+ },
+ "transparent": {
+ "type": "string"
+ },
+ "borderTransparent": {
+ "type": "string"
+ },
+ "contrastText": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "info": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "main": {
+ "type": "string"
+ },
+ "shade": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "border": {
+ "type": "string"
+ },
+ "transparent": {
+ "type": "string"
+ },
+ "borderTransparent": {
+ "type": "string"
+ },
+ "contrastText": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "main": {
+ "type": "string"
+ },
+ "shade": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "border": {
+ "type": "string"
+ },
+ "transparent": {
+ "type": "string"
+ },
+ "borderTransparent": {
+ "type": "string"
+ },
+ "contrastText": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "success": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "main": {
+ "type": "string"
+ },
+ "shade": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "border": {
+ "type": "string"
+ },
+ "transparent": {
+ "type": "string"
+ },
+ "borderTransparent": {
+ "type": "string"
+ },
+ "contrastText": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "warning": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "main": {
+ "type": "string"
+ },
+ "shade": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "border": {
+ "type": "string"
+ },
+ "transparent": {
+ "type": "string"
+ },
+ "borderTransparent": {
+ "type": "string"
+ },
+ "contrastText": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "text": {
+ "type": "object",
+ "properties": {
+ "primary": {
+ "type": "string"
+ },
+ "secondary": {
+ "type": "string"
+ },
+ "disabled": {
+ "type": "string"
+ },
+ "link": {
+ "type": "string"
+ },
+ "maxContrast": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "background": {
+ "type": "object",
+ "properties": {
+ "canvas": {
+ "type": "string"
+ },
+ "primary": {
+ "type": "string"
+ },
+ "secondary": {
+ "type": "string"
+ },
+ "elevated": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "border": {
+ "type": "object",
+ "properties": {
+ "weak": {
+ "type": "string"
+ },
+ "medium": {
+ "type": "string"
+ },
+ "strong": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "gradients": {
+ "type": "object",
+ "properties": {
+ "brandVertical": {
+ "type": "string"
+ },
+ "brandHorizontal": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "action": {
+ "type": "object",
+ "properties": {
+ "selected": {
+ "type": "string"
+ },
+ "selectedBorder": {
+ "type": "string"
+ },
+ "hover": {
+ "type": "string"
+ },
+ "hoverOpacity": {
+ "type": "number"
+ },
+ "focus": {
+ "type": "string"
+ },
+ "disabledBackground": {
+ "type": "string"
+ },
+ "disabledText": {
+ "type": "string"
+ },
+ "disabledOpacity": {
+ "type": "number"
+ }
+ },
+ "additionalProperties": false
+ },
+ "hoverFactor": {
+ "type": "number"
+ },
+ "contrastThreshold": {
+ "type": "number"
+ },
+ "tonalOffset": {
+ "type": "number"
+ }
+ },
+ "additionalProperties": false
+ },
+ "spacing": {
+ "type": "object",
+ "properties": {
+ "gridSize": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 9007199254740991
+ }
+ },
+ "additionalProperties": false
+ },
+ "shape": {
+ "type": "object",
+ "properties": {
+ "borderRadius": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 9007199254740991
+ }
+ },
+ "additionalProperties": false
+ },
+ "typography": {
+ "type": "object",
+ "properties": {
+ "fontFamily": {
+ "type": "string"
+ },
+ "fontFamilyMonospace": {
+ "type": "string"
+ },
+ "fontSize": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "fontWeightLight": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "fontWeightRegular": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "fontWeightMedium": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "fontWeightBold": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "htmlFontSize": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ }
+ },
+ "additionalProperties": false
+ },
+ "visualization": {
+ "type": "object",
+ "properties": {
+ "hues": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "const": "red"
+ },
+ "shades": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string",
+ "enum": ["super-light-red", "light-red", "red", "semi-dark-red", "dark-red"]
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "primary": {
+ "type": "boolean"
+ }
+ },
+ "required": ["color", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["name", "shades"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "const": "orange"
+ },
+ "shades": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string",
+ "enum": ["super-light-orange", "light-orange", "orange", "semi-dark-orange", "dark-orange"]
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "primary": {
+ "type": "boolean"
+ }
+ },
+ "required": ["color", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["name", "shades"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "const": "yellow"
+ },
+ "shades": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string",
+ "enum": ["super-light-yellow", "light-yellow", "yellow", "semi-dark-yellow", "dark-yellow"]
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "primary": {
+ "type": "boolean"
+ }
+ },
+ "required": ["color", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["name", "shades"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "const": "green"
+ },
+ "shades": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string",
+ "enum": ["super-light-green", "light-green", "green", "semi-dark-green", "dark-green"]
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "primary": {
+ "type": "boolean"
+ }
+ },
+ "required": ["color", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["name", "shades"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "const": "blue"
+ },
+ "shades": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string",
+ "enum": ["super-light-blue", "light-blue", "blue", "semi-dark-blue", "dark-blue"]
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "primary": {
+ "type": "boolean"
+ }
+ },
+ "required": ["color", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["name", "shades"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "const": "purple"
+ },
+ "shades": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string",
+ "enum": ["super-light-purple", "light-purple", "purple", "semi-dark-purple", "dark-purple"]
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "primary": {
+ "type": "boolean"
+ }
+ },
+ "required": ["color", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["name", "shades"],
+ "additionalProperties": false
+ }
+ ]
+ }
+ },
+ "palette": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": ["name", "id"],
+ "additionalProperties": false
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/aubergine.json b/packages/grafana-data/src/themes/themeDefinitions/aubergine.json
new file mode 100644
index 00000000000..4baf4f3f439
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/aubergine.json
@@ -0,0 +1,50 @@
+{
+ "name": "Aubergine",
+ "id": "aubergine",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "#4F2A3D",
+ "medium": "#6A3C4B",
+ "strong": "#8C5A69"
+ },
+ "text": {
+ "primary": "#E5D0D6",
+ "secondary": "#D1A8C4",
+ "disabled": "#B7A0A6",
+ "link": "#A56BB6",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#8C5A69"
+ },
+ "secondary": {
+ "main": "#6A3C4B",
+ "text": "#D1A8C4",
+ "border": "#8C5A69"
+ },
+ "background": {
+ "canvas": "#2E1F2D",
+ "primary": "#3C2136",
+ "secondary": "#4A2D47",
+ "elevated": "#4A2D47"
+ },
+ "action": {
+ "hover": "#6A3C4B",
+ "selected": "#8C5A69",
+ "selectedBorder": "#FFB300",
+ "focus": "#A56BB6",
+ "hoverOpacity": 0.1,
+ "disabledText": "#B7A0A6",
+ "disabledBackground": "#4A2D47",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #6A3C4B 0%, #A56BB6 100%)",
+ "brandVertical": "linear-gradient(0deg, #6A3C4B 0%, #A56BB6 100%)"
+ },
+ "contrastThreshold": 4,
+ "hoverFactor": 0.07,
+ "tonalOffset": 0.15
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/aubergine.ts b/packages/grafana-data/src/themes/themeDefinitions/aubergine.ts
deleted file mode 100644
index 967621ebc60..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/aubergine.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const aubergineTheme: NewThemeOptions = {
- name: 'Aubergine',
- colors: {
- mode: 'dark',
- border: {
- weak: '#4F2A3D',
- medium: '#6A3C4B',
- strong: '#8C5A69',
- },
- text: {
- primary: '#E5D0D6',
- secondary: '#D1A8C4',
- disabled: '#B7A0A6',
- link: '#A56BB6',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#8C5A69',
- },
- secondary: {
- main: '#6A3C4B',
- text: '#D1A8C4',
- border: '#8C5A69',
- },
- background: {
- canvas: '#2E1F2D',
- primary: '#3C2136',
- secondary: '#4A2D47',
- elevated: '#4A2D47',
- },
- action: {
- hover: '#6A3C4B',
- selected: '#8C5A69',
- selectedBorder: '#FFB300',
- focus: '#A56BB6',
- hoverOpacity: 0.1,
- disabledText: '#B7A0A6',
- disabledBackground: '#4A2D47',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #6A3C4B 0%, #A56BB6 100%)',
- brandVertical: 'linear-gradient(0deg, #6A3C4B 0%, #A56BB6 100%)',
- },
- contrastThreshold: 4,
- hoverFactor: 0.07,
- tonalOffset: 0.15,
- },
-};
-
-export default aubergineTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/debug.json b/packages/grafana-data/src/themes/themeDefinitions/debug.json
new file mode 100644
index 00000000000..a9cce4c5e21
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/debug.json
@@ -0,0 +1,60 @@
+{
+ "name": "Debug",
+ "id": "debug",
+ "colors": {
+ "mode": "dark",
+ "background": {
+ "canvas": "#000033",
+ "primary": "#000044",
+ "secondary": "#000055",
+ "elevated": "#000055"
+ },
+ "text": {
+ "primary": "#bbbb00",
+ "secondary": "#888800",
+ "disabled": "#444400",
+ "link": "#dddd00",
+ "maxContrast": "#ffff00"
+ },
+ "border": {
+ "weak": "#ff000044",
+ "medium": "#ff000088",
+ "strong": "#ff0000ff"
+ },
+ "primary": {
+ "border": "#ff000088",
+ "text": "#cccc00",
+ "contrastText": "#ffff00",
+ "shade": "#9900dd"
+ },
+ "secondary": {
+ "border": "#ff000088",
+ "text": "#cccc00",
+ "contrastText": "#ffff00",
+ "shade": "#9900dd"
+ },
+ "info": {
+ "shade": "#9900dd"
+ },
+ "warning": {
+ "shade": "#9900dd"
+ },
+ "success": {
+ "shade": "#9900dd"
+ },
+ "error": {
+ "shade": "#9900dd"
+ },
+ "action": {
+ "hover": "#9900dd",
+ "focus": "#6600aa",
+ "selected": "#440088"
+ }
+ },
+ "shape": {
+ "borderRadius": 8
+ },
+ "spacing": {
+ "gridSize": 10
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/debug.ts b/packages/grafana-data/src/themes/themeDefinitions/debug.ts
deleted file mode 100644
index 22e577faf2c..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/debug.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-/**
- * a very ugly theme that is useful for debugging and checking if the theme is applied correctly
- * borders are red,
- * backgrounds are blue,
- * text is yellow,
- * and grafana loves you <3
- * (also corners are rounded, action states (hover, focus, selected) are purple)
- */
-const debugTheme: NewThemeOptions = {
- name: 'Debug',
- colors: {
- mode: 'dark',
- background: {
- canvas: '#000033',
- primary: '#000044',
- secondary: '#000055',
- elevated: '#000055',
- },
- text: {
- primary: '#bbbb00',
- secondary: '#888800',
- disabled: '#444400',
- link: '#dddd00',
- maxContrast: '#ffff00',
- },
- border: {
- weak: '#ff000044',
- medium: '#ff000088',
- strong: '#ff0000ff',
- },
- primary: {
- border: '#ff000088',
- text: '#cccc00',
- contrastText: '#ffff00',
- shade: '#9900dd',
- },
- secondary: {
- border: '#ff000088',
- text: '#cccc00',
- contrastText: '#ffff00',
- shade: '#9900dd',
- },
- info: {
- shade: '#9900dd',
- },
- warning: {
- shade: '#9900dd',
- },
- success: {
- shade: '#9900dd',
- },
- error: {
- shade: '#9900dd',
- },
- action: {
- hover: '#9900dd',
- focus: '#6600aa',
- selected: '#440088',
- },
- },
- shape: {
- borderRadius: 8,
- },
- spacing: {
- gridSize: 10,
- },
-};
-
-export default debugTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/desertbloom.json b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.json
new file mode 100644
index 00000000000..1c2304aaff8
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.json
@@ -0,0 +1,71 @@
+{
+ "name": "Desert bloom",
+ "id": "desertbloom",
+ "colors": {
+ "mode": "light",
+ "border": {
+ "weak": "rgba(0, 0, 0, 0.12)",
+ "medium": "rgba(0, 0, 0, 0.20)",
+ "strong": "rgba(0, 0, 0, 0.30)"
+ },
+ "text": {
+ "primary": "#333333",
+ "secondary": "#555555",
+ "disabled": "rgba(0, 0, 0, 0.5)",
+ "link": "#1A82E2",
+ "maxContrast": "#000000"
+ },
+ "primary": {
+ "main": "#FF6F61",
+ "text": "#FE6F61",
+ "border": "#E55B4D",
+ "name": "primary",
+ "shade": "#E55B4D",
+ "transparent": "#FF6F6126",
+ "contrastText": "#FFFFFF",
+ "borderTransparent": "#FF6F6140"
+ },
+ "secondary": {
+ "main": "#FFFFFF",
+ "text": "#695f53",
+ "border": "#d9cec0",
+ "name": "secondary",
+ "shade": "#d9cec0",
+ "transparent": "#FFFFFF26",
+ "contrastText": "#4c4339",
+ "borderTransparent": "#FFFFFF40"
+ },
+ "info": {
+ "main": "#1A82E2"
+ },
+ "success": {
+ "main": "#4CAF50"
+ },
+ "warning": {
+ "main": "#FFC107"
+ },
+ "background": {
+ "canvas": "#FFF8F0",
+ "primary": "#FFFFFF",
+ "secondary": "#f9f3e8",
+ "elevated": "#FFFFFF"
+ },
+ "action": {
+ "hover": "rgba(168, 156, 134, 0.12)",
+ "selected": "rgba(168, 156, 134, 0.36)",
+ "selectedBorder": "#FF6F61",
+ "focus": "rgba(168, 156, 134, 0.50)",
+ "hoverOpacity": 0.08,
+ "disabledText": "rgba(168, 156, 134, 0.5)",
+ "disabledBackground": "rgba(168, 156, 134, 0.06)",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg,rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)",
+ "brandVertical": "linear-gradient(0deg, rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.03,
+ "tonalOffset": 0.15
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts
deleted file mode 100644
index 8a86b73a0f7..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const desertBloomTheme: NewThemeOptions = {
- name: 'Desert bloom',
- colors: {
- mode: 'light',
- border: {
- weak: 'rgba(0, 0, 0, 0.12)',
- medium: 'rgba(0, 0, 0, 0.20)',
- strong: 'rgba(0, 0, 0, 0.30)',
- },
- text: {
- primary: '#333333',
- secondary: '#555555',
- disabled: 'rgba(0, 0, 0, 0.5)',
- link: '#1A82E2',
- maxContrast: '#000000',
- },
- primary: {
- main: '#FF6F61',
- text: '#FE6F61',
- border: '#E55B4D',
- name: 'primary',
- shade: '#E55B4D',
- transparent: '#FF6F6126',
- contrastText: '#FFFFFF',
- borderTransparent: '#FF6F6140',
- },
- secondary: {
- main: '#FFFFFF',
- text: '#695f53',
- border: '#d9cec0',
- name: 'secondary',
- shade: '#d9cec0',
- transparent: '#FFFFFF26',
- contrastText: '#4c4339',
- borderTransparent: '#FFFFFF40',
- },
- info: {
- main: '#1A82E2',
- },
- success: {
- main: '#4CAF50',
- },
- warning: {
- main: '#FFC107',
- },
- background: {
- canvas: '#FFF8F0',
- primary: '#FFFFFF',
- secondary: '#f9f3e8',
- elevated: '#FFFFFF',
- },
- action: {
- hover: 'rgba(168, 156, 134, 0.12)',
- selected: 'rgba(168, 156, 134, 0.36)',
- selectedBorder: '#FF6F61',
- focus: 'rgba(168, 156, 134, 0.50)',
- hoverOpacity: 0.08,
- disabledText: 'rgba(168, 156, 134, 0.5)',
- disabledBackground: 'rgba(168, 156, 134, 0.06)',
- disabledOpacity: 0.38,
- },
-
- gradients: {
- brandHorizontal: 'linear-gradient(270deg,rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)',
- brandVertical: 'linear-gradient(0deg, rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.03,
- tonalOffset: 0.15,
- },
-};
-
-export default desertBloomTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.json b/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.json
new file mode 100644
index 00000000000..a147afbbe76
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.json
@@ -0,0 +1,62 @@
+{
+ "name": "Gilded grove",
+ "id": "gildedgrove",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "rgba(200, 200, 180, 0.12)",
+ "medium": "rgba(200, 200, 180, 0.20)",
+ "strong": "rgba(200, 200, 180, 0.30)"
+ },
+ "text": {
+ "primary": "rgb(250, 250, 239)",
+ "secondary": "rgba(200, 200, 180, 0.85)",
+ "disabled": "rgba(200, 200, 180, 0.6)",
+ "link": "#FEAC34",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#FEAC34",
+ "text": "#FFD783",
+ "border": "#FFD783",
+ "name": "primary",
+ "shade": "rgb(255, 173, 80)",
+ "transparent": "#FEAC3426",
+ "contrastText": "#111614",
+ "borderTransparent": "#FFD78340"
+ },
+ "secondary": {
+ "main": "rgba(200, 200, 180, 0.10)",
+ "shade": "rgba(200, 200, 180, 0.14)",
+ "transparent": "rgba(200, 200, 180, 0.08)",
+ "text": "rgb(200, 200, 180)",
+ "contrastText": "rgb(200, 200, 180)",
+ "border": "rgba(200, 200, 180, 0.08)",
+ "name": "secondary",
+ "borderTransparent": "rgba(200, 200, 180, 0.25)"
+ },
+ "background": {
+ "canvas": "#111614",
+ "primary": "#1d2220",
+ "secondary": "#27312E",
+ "elevated": "#27312E"
+ },
+ "action": {
+ "hover": "rgba(200, 200, 180, 0.16)",
+ "selected": "rgba(200, 200, 180, 0.12)",
+ "selectedBorder": "#FEAC34",
+ "focus": "rgba(200, 200, 180, 0.16)",
+ "hoverOpacity": 0.08,
+ "disabledText": "rgba(200, 200, 180, 0.6)",
+ "disabledBackground": "rgba(200, 200, 180, 0.04)",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #FEAC34 0%, #FFD783 100%)",
+ "brandVertical": "linear-gradient(0.01deg, #FEAC34 0.01%, #FFD783 99.99%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.03,
+ "tonalOffset": 0.15
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts b/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts
deleted file mode 100644
index bfa3e121329..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const gildedGroveTheme: NewThemeOptions = {
- name: 'Gilded grove',
- colors: {
- mode: 'dark',
- border: {
- weak: 'rgba(200, 200, 180, 0.12)',
- medium: 'rgba(200, 200, 180, 0.20)',
- strong: 'rgba(200, 200, 180, 0.30)',
- },
- text: {
- primary: 'rgb(250, 250, 239)',
- secondary: 'rgba(200, 200, 180, 0.85)',
- disabled: 'rgba(200, 200, 180, 0.6)',
- link: '#FEAC34',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#FEAC34',
- text: '#FFD783',
- border: '#FFD783',
- name: 'primary',
- shade: 'rgb(255, 173, 80)',
- transparent: '#FEAC3426',
- contrastText: '#111614',
- borderTransparent: '#FFD78340',
- },
- secondary: {
- main: 'rgba(200, 200, 180, 0.10)',
- shade: 'rgba(200, 200, 180, 0.14)',
- transparent: 'rgba(200, 200, 180, 0.08)',
- text: 'rgb(200, 200, 180)',
- contrastText: 'rgb(200, 200, 180)',
- border: 'rgba(200, 200, 180, 0.08)',
- name: 'secondary',
- borderTransparent: 'rgba(200, 200, 180, 0.25)',
- },
- background: {
- canvas: '#111614',
- primary: '#1d2220',
- secondary: '#27312E',
- elevated: '#27312E',
- },
- action: {
- hover: 'rgba(200, 200, 180, 0.16)',
- selected: 'rgba(200, 200, 180, 0.12)',
- selectedBorder: '#FEAC34',
- focus: 'rgba(200, 200, 180, 0.16)',
- hoverOpacity: 0.08,
- disabledText: 'rgba(200, 200, 180, 0.6)',
- disabledBackground: 'rgba(200, 200, 180, 0.04)',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #FEAC34 0%, #FFD783 100%)',
- brandVertical: 'linear-gradient(0.01deg, #FEAC34 0.01%, #FFD783 99.99%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.03,
- tonalOffset: 0.15,
- },
-};
-
-export default gildedGroveTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/gloom.json b/packages/grafana-data/src/themes/themeDefinitions/gloom.json
new file mode 100644
index 00000000000..8558c942511
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/gloom.json
@@ -0,0 +1,52 @@
+{
+ "name": "Gloom",
+ "id": "gloom",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "rgba(210, 210, 220, 0.12)",
+ "medium": "rgba(210, 210, 220, 0.20)",
+ "strong": "rgba(210, 210, 220, 0.30)"
+ },
+ "text": {
+ "primary": "rgb(210, 210, 220)",
+ "secondary": "rgba(210, 210, 220, 0.65)",
+ "disabled": "rgba(210, 210, 220, 0.48)",
+ "link": "#f99a5c",
+ "maxContrast": "#FFF"
+ },
+ "primary": {
+ "main": "#ff934d",
+ "text": "#f99a5c",
+ "border": "#ff934d",
+ "name": "primary"
+ },
+ "secondary": {
+ "main": "rgba(195, 195, 245, 0.10)",
+ "shade": "rgba(195, 195, 245, 0.14)",
+ "transparent": "rgba(195, 195, 245, 0.08)",
+ "text": "rgba(195, 195, 245)",
+ "contrastText": "rgb(195, 195, 245)",
+ "border": "rgba(195, 195, 245, 0.08)"
+ },
+ "background": {
+ "canvas": "#000",
+ "primary": "#121118",
+ "secondary": "#211e28",
+ "elevated": "#211e28"
+ },
+ "action": {
+ "hover": "rgba(195, 195, 245, 0.07)",
+ "selected": "rgba(195, 195, 245, 0.11)",
+ "selectedBorder": "#ff934d",
+ "focus": "rgba(195, 195, 245, 0.07)",
+ "hoverOpacity": 0.05,
+ "disabledText": "rgba(210, 210, 220, 0.48)",
+ "disabledBackground": "rgba(210, 210, 220, 0.04)",
+ "disabledOpacity": 0.38
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.03,
+ "tonalOffset": 0.15
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/gloom.ts b/packages/grafana-data/src/themes/themeDefinitions/gloom.ts
deleted file mode 100644
index 49c105626fb..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/gloom.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-/**
- * Torkel's GrafanaCon theme
- * very WIP state
- */
-
-const whiteBase = `210, 210, 220`;
-const secondaryBase = `195, 195, 245`;
-
-//const brandMain = '#3d71d9';
-//const brandText = '#6e9fff';
-const brandMain = '#ff934d';
-const brandText = '#f99a5c';
-const disabledText = `rgba(${whiteBase}, 0.48)`;
-
-const gloomTheme: NewThemeOptions = {
- name: 'Gloom',
- colors: {
- mode: 'dark',
- border: {
- weak: `rgba(${whiteBase}, 0.12)`,
- medium: `rgba(${whiteBase}, 0.20)`,
- strong: `rgba(${whiteBase}, 0.30)`,
- },
-
- text: {
- primary: `rgb(${whiteBase})`,
- secondary: `rgba(${whiteBase}, 0.65)`,
- disabled: disabledText,
- link: brandText,
- maxContrast: '#FFF',
- },
-
- primary: {
- main: brandMain,
- text: brandText,
- border: brandMain,
- name: 'primary',
- },
-
- secondary: {
- main: `rgba(${secondaryBase}, 0.10)`,
- shade: `rgba(${secondaryBase}, 0.14)`,
- transparent: `rgba(${secondaryBase}, 0.08)`,
- text: `rgba(${secondaryBase})`,
- contrastText: `rgb(${secondaryBase})`,
- border: `rgba(${secondaryBase}, 0.08)`,
- },
-
- background: {
- canvas: '#000',
- primary: '#121118',
- secondary: '#211e28',
- elevated: '#211e28',
- },
-
- action: {
- hover: `rgba(${secondaryBase}, 0.07)`,
- selected: `rgba(${secondaryBase}, 0.11)`,
- selectedBorder: brandMain,
- focus: `rgba(${secondaryBase}, 0.07)`,
- hoverOpacity: 0.05,
- disabledText: disabledText,
- disabledBackground: `rgba(${whiteBase}, 0.04)`,
- disabledOpacity: 0.38,
- },
-
- // gradients: {
- // brandHorizontal: 'linear-gradient(270deg, #ff934d 0%, #FEAC34 100%)',
- // brandVertical: 'linear-gradient(0.01deg, #ff934d 0.01%, #FEAC34 99.99%)',
- // },
-
- contrastThreshold: 3,
- hoverFactor: 0.03,
- tonalOffset: 0.15,
- },
-};
-
-export default gloomTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/index.ts b/packages/grafana-data/src/themes/themeDefinitions/index.ts
deleted file mode 100644
index 151ae00593e..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export { default as aubergine } from './aubergine';
-export { default as debug } from './debug';
-export { default as desertbloom } from './desertbloom';
-export { default as gildedgrove } from './gildedgrove';
-export { default as mars } from './mars';
-export { default as matrix } from './matrix';
-export { default as sapphiredusk } from './sapphiredusk';
-export { default as synthwave } from './synthwave';
-export { default as tron } from './tron';
-export { default as victorian } from './victorian';
-export { default as zen } from './zen';
-export { default as gloom } from './gloom';
diff --git a/packages/grafana-data/src/themes/themeDefinitions/mars.json b/packages/grafana-data/src/themes/themeDefinitions/mars.json
new file mode 100644
index 00000000000..1aeb874f018
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/mars.json
@@ -0,0 +1,50 @@
+{
+ "name": "Mars",
+ "id": "mars",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "rgba(210, 90, 60, 0.2)",
+ "medium": "rgba(210, 90, 60, 0.35)",
+ "strong": "rgba(210, 90, 60, 0.5)"
+ },
+ "text": {
+ "primary": "#DDDDDD",
+ "secondary": "#BBBBBB",
+ "disabled": "rgba(221, 221, 221, 0.5)",
+ "link": "#FF6F61",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#FF6F61"
+ },
+ "secondary": {
+ "main": "#6a2f2f",
+ "text": "#BBBBBB",
+ "border": "rgba(210, 90, 60, 0.2)"
+ },
+ "background": {
+ "canvas": "#3C1E1E",
+ "primary": "#522626",
+ "secondary": "#6A2F2F",
+ "elevated": "#6A2F2F"
+ },
+ "action": {
+ "hover": "rgba(210, 90, 60, 0.16)",
+ "selected": "rgba(210, 90, 60, 0.12)",
+ "selectedBorder": "#FF6F61",
+ "focus": "rgba(210, 90, 60, 0.16)",
+ "hoverOpacity": 0.08,
+ "disabledText": "rgba(221, 221, 221, 0.5)",
+ "disabledBackground": "rgba(210, 90, 60, 0.08)",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #FF6F61 0%, #D25A3C 100%)",
+ "brandVertical": "linear-gradient(0.01deg, #FF6F61 0.01%, #D25A3C 99.99%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.05,
+ "tonalOffset": 0.2
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/mars.ts b/packages/grafana-data/src/themes/themeDefinitions/mars.ts
deleted file mode 100644
index f1db51e23b2..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/mars.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const marsTheme: NewThemeOptions = {
- name: 'Mars',
- colors: {
- mode: 'dark',
- border: {
- weak: 'rgba(210, 90, 60, 0.2)',
- medium: 'rgba(210, 90, 60, 0.35)',
- strong: 'rgba(210, 90, 60, 0.5)',
- },
- text: {
- primary: '#DDDDDD',
- secondary: '#BBBBBB',
- disabled: 'rgba(221, 221, 221, 0.5)',
- link: '#FF6F61',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#FF6F61',
- },
- secondary: {
- main: '#6a2f2f',
- text: '#BBBBBB',
- border: 'rgba(210, 90, 60, 0.2)',
- },
- background: {
- canvas: '#3C1E1E',
- primary: '#522626',
- secondary: '#6A2F2F',
- elevated: '#6A2F2F',
- },
- action: {
- hover: 'rgba(210, 90, 60, 0.16)',
- selected: 'rgba(210, 90, 60, 0.12)',
- selectedBorder: '#FF6F61',
- focus: 'rgba(210, 90, 60, 0.16)',
- hoverOpacity: 0.08,
- disabledText: 'rgba(221, 221, 221, 0.5)',
- disabledBackground: 'rgba(210, 90, 60, 0.08)',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #FF6F61 0%, #D25A3C 100%)',
- brandVertical: 'linear-gradient(0.01deg, #FF6F61 0.01%, #D25A3C 99.99%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.05,
- tonalOffset: 0.2,
- },
-};
-
-export default marsTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/matrix.json b/packages/grafana-data/src/themes/themeDefinitions/matrix.json
new file mode 100644
index 00000000000..a64a7ccce40
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/matrix.json
@@ -0,0 +1,41 @@
+{
+ "name": "Matrix",
+ "id": "matrix",
+ "colors": {
+ "mode": "dark",
+ "background": {
+ "canvas": "#000000",
+ "primary": "#020202",
+ "secondary": "#080808",
+ "elevated": "#080808"
+ },
+ "text": {
+ "primary": "#00c017",
+ "secondary": "#008910",
+ "disabled": "#006a0c",
+ "link": "#00ff41",
+ "maxContrast": "#00ff41"
+ },
+ "border": {
+ "weak": "#008f1144",
+ "medium": "#008f1188",
+ "strong": "#008910"
+ },
+ "primary": {
+ "main": "#008910"
+ },
+ "secondary": {
+ "text": "#008910"
+ },
+ "gradients": {
+ "brandVertical": "linear-gradient(0deg, #008910 0%, #00ff41 100%)",
+ "brandHorizontal": "linear-gradient(90deg, #008910 0%, #00ff41 100%)"
+ }
+ },
+ "shape": {
+ "borderRadius": 0
+ },
+ "typography": {
+ "fontFamily": "monospace"
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/matrix.ts b/packages/grafana-data/src/themes/themeDefinitions/matrix.ts
deleted file mode 100644
index 51c58b9b394..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/matrix.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const matrixTheme: NewThemeOptions = {
- name: 'Matrix',
- colors: {
- mode: 'dark',
- background: {
- canvas: '#000000',
- primary: '#020202',
- secondary: '#080808',
- elevated: '#080808',
- },
- text: {
- primary: '#00c017',
- secondary: '#008910',
- disabled: '#006a0c',
- link: '#00ff41',
- maxContrast: '#00ff41',
- },
- border: {
- weak: '#008f1144',
- medium: '#008f1188',
- strong: '#008910',
- },
- primary: {
- main: '#008910',
- },
- secondary: {
- text: '#008910',
- },
- gradients: {
- brandVertical: 'linear-gradient(0deg, #008910 0%, #00ff41 100%)',
- brandHorizontal: 'linear-gradient(90deg, #008910 0%, #00ff41 100%)',
- },
- },
- shape: {
- borderRadius: 0,
- },
- typography: {
- fontFamily: 'monospace',
- },
-};
-
-export default matrixTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.json b/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.json
new file mode 100644
index 00000000000..8d5f7731f05
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.json
@@ -0,0 +1,76 @@
+{
+ "name": "Sapphire dusk",
+ "id": "sapphiredusk",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "#232e47",
+ "medium": "#2c3853",
+ "strong": "#404d6b"
+ },
+ "text": {
+ "primary": "#FFFFFF",
+ "secondary": "#bcccdd",
+ "disabled": "#838da5",
+ "link": "#93EBF0",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#93EBF0",
+ "text": "#a8e9ed",
+ "border": "#93ebf0",
+ "name": "primary",
+ "shade": "#c0f5d9",
+ "transparent": "#93EBF029",
+ "contrastText": "#111614",
+ "borderTransparent": "#93ebf040"
+ },
+ "secondary": {
+ "main": "#2c364f",
+ "shade": "#36415e",
+ "transparent": "rgba(200, 200, 180, 0.08)",
+ "text": "#d1dfff",
+ "contrastText": "#acfeff",
+ "border": "rgba(200, 200, 180, 0.08)",
+ "name": "secondary",
+ "borderTransparent": "rgba(200, 200, 180, 0.25)"
+ },
+ "info": {
+ "main": "#4d4593",
+ "text": "#a8e9ed",
+ "border": "#5d54a7"
+ },
+ "error": {
+ "main": "#c63370"
+ },
+ "success": {
+ "main": "#1A7F4B"
+ },
+ "warning": {
+ "main": "#D448EA"
+ },
+ "background": {
+ "canvas": "#1e273d",
+ "primary": "#12192e",
+ "secondary": "#212c47",
+ "elevated": "#212c47"
+ },
+ "action": {
+ "hover": "#364057",
+ "selected": "#364260",
+ "selectedBorder": "#D448EA",
+ "focus": "#364057",
+ "hoverOpacity": 0.08,
+ "disabledText": "#838da5",
+ "disabledBackground": "rgba(54, 64, 87, 0.2)",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #D346EF 0%, #2C83FE 100%)",
+ "brandVertical": "linear-gradient(0deg, #D346EF 0%, #2C83FE 100%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.03,
+ "tonalOffset": 0.15
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts b/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts
deleted file mode 100644
index c777c61b055..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const sapphireDuskTheme: NewThemeOptions = {
- name: 'Sapphire dusk',
- colors: {
- mode: 'dark',
- border: {
- weak: '#232e47',
- medium: '#2c3853',
- strong: '#404d6b',
- },
- text: {
- primary: '#FFFFFF',
- secondary: '#bcccdd',
- disabled: '#838da5',
- link: '#93EBF0',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#93EBF0',
- text: '#a8e9ed',
- border: '#93ebf0',
- name: 'primary',
- shade: '#c0f5d9',
- transparent: '#93EBF029',
- contrastText: '#111614',
- borderTransparent: '#93ebf040',
- },
- secondary: {
- main: '#2c364f',
- shade: '#36415e',
- transparent: 'rgba(200, 200, 180, 0.08)',
- text: '#d1dfff',
- contrastText: '#acfeff',
- border: 'rgba(200, 200, 180, 0.08)',
- name: 'secondary',
- borderTransparent: 'rgba(200, 200, 180, 0.25)',
- },
- info: {
- main: '#4d4593',
- text: '#a8e9ed',
- border: '#5d54a7',
- },
- error: {
- main: '#c63370',
- },
- success: {
- main: '#1A7F4B',
- },
- warning: {
- main: '#D448EA',
- },
- background: {
- canvas: '#1e273d',
- primary: '#12192e',
- secondary: '#212c47',
- elevated: '#212c47',
- },
- action: {
- hover: '#364057',
- selected: '#364260',
- selectedBorder: '#D448EA',
- focus: '#364057',
- hoverOpacity: 0.08,
- disabledText: '#838da5',
- disabledBackground: 'rgba(54, 64, 87, 0.2)',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #D346EF 0%, #2C83FE 100%)',
- brandVertical: 'linear-gradient(0deg, #D346EF 0%, #2C83FE 100%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.03,
- tonalOffset: 0.15,
- },
-};
-
-export default sapphireDuskTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/synthwave.json b/packages/grafana-data/src/themes/themeDefinitions/synthwave.json
new file mode 100644
index 00000000000..377f09f2585
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/synthwave.json
@@ -0,0 +1,50 @@
+{
+ "name": "Synthwave",
+ "id": "synthwave",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "rgba(255, 20, 147, 0.12)",
+ "medium": "rgba(255, 20, 147, 0.20)",
+ "strong": "rgba(255, 20, 147, 0.30)"
+ },
+ "text": {
+ "primary": "#E0E0E0",
+ "secondary": "rgba(224, 224, 224, 0.75)",
+ "disabled": "rgba(224, 224, 224, 0.5)",
+ "link": "#FF69B4",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#FF1493"
+ },
+ "secondary": {
+ "main": "#37183a",
+ "text": "rgba(224, 224, 224, 0.75)",
+ "border": "rgba(255, 20, 147, 0.10)"
+ },
+ "background": {
+ "canvas": "#1A1A2E",
+ "primary": "#16213E",
+ "secondary": "#0F3460",
+ "elevated": "#0F3460"
+ },
+ "action": {
+ "hover": "rgba(255, 20, 147, 0.16)",
+ "selected": "rgba(255, 20, 147, 0.12)",
+ "selectedBorder": "#FF1493",
+ "focus": "rgba(255, 20, 147, 0.16)",
+ "hoverOpacity": 0.08,
+ "disabledText": "rgba(224, 224, 224, 0.5)",
+ "disabledBackground": "rgba(255, 20, 147, 0.08)",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #FF1493 0%, #1E90FF 100%)",
+ "brandVertical": "linear-gradient(0.01deg, #FF1493 0.01%, #1E90FF 99.99%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.03,
+ "tonalOffset": 0.15
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/synthwave.ts b/packages/grafana-data/src/themes/themeDefinitions/synthwave.ts
deleted file mode 100644
index 5fc53cda0bb..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/synthwave.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const synthwaveTheme: NewThemeOptions = {
- name: 'Synthwave',
- colors: {
- mode: 'dark',
- border: {
- weak: 'rgba(255, 20, 147, 0.12)',
- medium: 'rgba(255, 20, 147, 0.20)',
- strong: 'rgba(255, 20, 147, 0.30)',
- },
- text: {
- primary: '#E0E0E0',
- secondary: 'rgba(224, 224, 224, 0.75)',
- disabled: 'rgba(224, 224, 224, 0.5)',
- link: '#FF69B4',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#FF1493',
- },
- secondary: {
- main: '#37183a',
- text: 'rgba(224, 224, 224, 0.75)',
- border: 'rgba(255, 20, 147, 0.10)',
- },
- background: {
- canvas: '#1A1A2E',
- primary: '#16213E',
- secondary: '#0F3460',
- elevated: '#0F3460',
- },
- action: {
- hover: 'rgba(255, 20, 147, 0.16)',
- selected: 'rgba(255, 20, 147, 0.12)',
- selectedBorder: '#FF1493',
- focus: 'rgba(255, 20, 147, 0.16)',
- hoverOpacity: 0.08,
- disabledText: 'rgba(224, 224, 224, 0.5)',
- disabledBackground: 'rgba(255, 20, 147, 0.08)',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #FF1493 0%, #1E90FF 100%)',
- brandVertical: 'linear-gradient(0.01deg, #FF1493 0.01%, #1E90FF 99.99%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.03,
- tonalOffset: 0.15,
- },
-};
-
-export default synthwaveTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/tron.json b/packages/grafana-data/src/themes/themeDefinitions/tron.json
new file mode 100644
index 00000000000..a92cf07fcb0
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/tron.json
@@ -0,0 +1,50 @@
+{
+ "name": "Tron",
+ "id": "tron",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "rgba(0, 255, 255, 0.12)",
+ "medium": "rgba(0, 255, 255, 0.20)",
+ "strong": "rgba(0, 255, 255, 0.30)"
+ },
+ "text": {
+ "primary": "#E0E0E0",
+ "secondary": "rgba(224, 224, 224, 0.75)",
+ "disabled": "rgba(224, 224, 224, 0.5)",
+ "link": "#00FFFF",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#00FFFF"
+ },
+ "secondary": {
+ "main": "#0b2e36",
+ "text": "rgba(224, 224, 224, 0.75)",
+ "border": "rgba(0, 255, 255, 0.10)"
+ },
+ "background": {
+ "canvas": "#0A0F18",
+ "primary": "#0F1B2A",
+ "secondary": "#152234",
+ "elevated": "#152234"
+ },
+ "action": {
+ "hover": "rgba(0, 255, 255, 0.16)",
+ "selected": "rgba(0, 255, 255, 0.12)",
+ "selectedBorder": "#00FFFF",
+ "focus": "rgba(0, 255, 255, 0.16)",
+ "hoverOpacity": 0.08,
+ "disabledText": "rgba(224, 224, 224, 0.5)",
+ "disabledBackground": "rgba(0, 255, 255, 0.08)",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #00FFFF 0%, #29ABE2 100%)",
+ "brandVertical": "linear-gradient(0.01deg, #00FFFF 0.01%, #29ABE2 99.99%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.05,
+ "tonalOffset": 0.2
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/tron.ts b/packages/grafana-data/src/themes/themeDefinitions/tron.ts
deleted file mode 100644
index a9f0b8c3ed4..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/tron.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const tronTheme: NewThemeOptions = {
- name: 'Tron',
- colors: {
- mode: 'dark',
- border: {
- weak: 'rgba(0, 255, 255, 0.12)',
- medium: 'rgba(0, 255, 255, 0.20)',
- strong: 'rgba(0, 255, 255, 0.30)',
- },
- text: {
- primary: '#E0E0E0',
- secondary: 'rgba(224, 224, 224, 0.75)',
- disabled: 'rgba(224, 224, 224, 0.5)',
- link: '#00FFFF',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#00FFFF',
- },
- secondary: {
- main: '#0b2e36',
- text: 'rgba(224, 224, 224, 0.75)',
- border: 'rgba(0, 255, 255, 0.10)',
- },
- background: {
- canvas: '#0A0F18',
- primary: '#0F1B2A',
- secondary: '#152234',
- elevated: '#152234',
- },
- action: {
- hover: 'rgba(0, 255, 255, 0.16)',
- selected: 'rgba(0, 255, 255, 0.12)',
- selectedBorder: '#00FFFF',
- focus: 'rgba(0, 255, 255, 0.16)',
- hoverOpacity: 0.08,
- disabledText: 'rgba(224, 224, 224, 0.5)',
- disabledBackground: 'rgba(0, 255, 255, 0.08)',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #00FFFF 0%, #29ABE2 100%)',
- brandVertical: 'linear-gradient(0.01deg, #00FFFF 0.01%, #29ABE2 99.99%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.05,
- tonalOffset: 0.2,
- },
-};
-
-export default tronTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/victorian.json b/packages/grafana-data/src/themes/themeDefinitions/victorian.json
new file mode 100644
index 00000000000..14483578450
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/victorian.json
@@ -0,0 +1,54 @@
+{
+ "name": "Victorian",
+ "id": "victorian",
+ "colors": {
+ "mode": "dark",
+ "border": {
+ "weak": "#3A2C22",
+ "medium": "#3A2C22",
+ "strong": "#4B3D32"
+ },
+ "text": {
+ "primary": "#D9D0A2",
+ "secondary": "#C4B89B",
+ "disabled": "#A89F91",
+ "link": "#C28A4D",
+ "maxContrast": "#FFFFFF"
+ },
+ "primary": {
+ "main": "#C28A4D"
+ },
+ "secondary": {
+ "main": "#3A2C22",
+ "text": "#C4B89B",
+ "border": "#4B3D32"
+ },
+ "background": {
+ "canvas": "#1F1510",
+ "primary": "#2C1A13",
+ "secondary": "#402A21",
+ "elevated": "#402A21"
+ },
+ "action": {
+ "hover": "#3A2C22",
+ "selected": "#4B3D32",
+ "selectedBorder": "#C28A4D",
+ "focus": "#C28A4D",
+ "hoverOpacity": 0.1,
+ "disabledText": "#A89F91",
+ "disabledBackground": "#402A21",
+ "disabledOpacity": 0.38
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #D9D0a1 0%, #C28A4D 100%)",
+ "brandVertical": "linear-gradient(0.01deg, #D9D0a1 0.01%, #C28A4D 99.99%)"
+ },
+ "contrastThreshold": 4,
+ "hoverFactor": 0.07,
+ "tonalOffset": 0.15
+ },
+ "typography": {
+ "fontFamily": "\"Georgia\", \"Times New Roman\", serif",
+ "fontFamilyMonospace": "'Courier New', monospace"
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/victorian.ts b/packages/grafana-data/src/themes/themeDefinitions/victorian.ts
deleted file mode 100644
index 32ddbcb244e..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/victorian.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const victorianTheme: NewThemeOptions = {
- name: 'Victorian',
- colors: {
- mode: 'dark',
- border: {
- weak: '#3A2C22',
- medium: '#3A2C22',
- strong: '#4B3D32',
- },
- text: {
- primary: '#D9D0A2',
- secondary: '#C4B89B',
- disabled: '#A89F91',
- link: '#C28A4D',
- maxContrast: '#FFFFFF',
- },
- primary: {
- main: '#C28A4D',
- },
- secondary: {
- main: '#3A2C22',
- text: '#C4B89B',
- border: '#4B3D32',
- },
- background: {
- canvas: '#1F1510',
- primary: '#2C1A13',
- secondary: '#402A21',
- elevated: '#402A21',
- },
- action: {
- hover: '#3A2C22',
- selected: '#4B3D32',
- selectedBorder: '#C28A4D',
- focus: '#C28A4D',
- hoverOpacity: 0.1,
- disabledText: '#A89F91',
- disabledBackground: '#402A21',
- disabledOpacity: 0.38,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #D9D0a1 0%, #C28A4D 100%)',
- brandVertical: 'linear-gradient(0.01deg, #D9D0a1 0.01%, #C28A4D 99.99%)',
- },
- contrastThreshold: 4,
- hoverFactor: 0.07,
- tonalOffset: 0.15,
- },
- typography: {
- fontFamily: '"Georgia", "Times New Roman", serif',
- fontFamilyMonospace: "'Courier New', monospace",
- },
-};
-
-export default victorianTheme;
diff --git a/packages/grafana-data/src/themes/themeDefinitions/zen.json b/packages/grafana-data/src/themes/themeDefinitions/zen.json
new file mode 100644
index 00000000000..99a8b900052
--- /dev/null
+++ b/packages/grafana-data/src/themes/themeDefinitions/zen.json
@@ -0,0 +1,50 @@
+{
+ "name": "Zen",
+ "id": "zen",
+ "colors": {
+ "mode": "light",
+ "text": {
+ "primary": "#333333",
+ "secondary": "#666666",
+ "disabled": "#B8B8B8",
+ "link": "#4F9F6E",
+ "maxContrast": "#000000"
+ },
+ "border": {
+ "weak": "#B1B7B3",
+ "medium": "#A2A8A2",
+ "strong": "#7C7F7A"
+ },
+ "primary": {
+ "main": "#6D8E6D"
+ },
+ "secondary": {
+ "main": "#E0E0E0",
+ "text": "#666666",
+ "border": "#A2A8A2"
+ },
+ "background": {
+ "canvas": "#F4F4F4",
+ "primary": "#E9E9E9",
+ "secondary": "#D8D8D8",
+ "elevated": "#E9E9E9"
+ },
+ "action": {
+ "hover": "#D1D1D1",
+ "selected": "#B8B8B8",
+ "selectedBorder": "#88B88B",
+ "hoverOpacity": 0.1,
+ "focus": "#D1D1D1",
+ "disabledBackground": "#E0E0E0",
+ "disabledText": "#B8B8B8",
+ "disabledOpacity": 0.5
+ },
+ "gradients": {
+ "brandHorizontal": "linear-gradient(270deg, #88B88B 0%, #6D8E6D 100%)",
+ "brandVertical": "linear-gradient(0.01deg, #88B88B 0.01%, #6D8E6D 99.99%)"
+ },
+ "contrastThreshold": 3,
+ "hoverFactor": 0.03,
+ "tonalOffset": 0.2
+ }
+}
diff --git a/packages/grafana-data/src/themes/themeDefinitions/zen.ts b/packages/grafana-data/src/themes/themeDefinitions/zen.ts
deleted file mode 100644
index f2735f41b74..00000000000
--- a/packages/grafana-data/src/themes/themeDefinitions/zen.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NewThemeOptions } from '../createTheme';
-
-const zenTheme: NewThemeOptions = {
- name: 'Zen',
- colors: {
- mode: 'light',
- text: {
- primary: '#333333',
- secondary: '#666666',
- disabled: '#B8B8B8',
- link: '#4F9F6E',
- maxContrast: '#000000',
- },
- border: {
- weak: '#B1B7B3',
- medium: '#A2A8A2',
- strong: '#7C7F7A',
- },
- primary: {
- main: '#6D8E6D',
- },
- secondary: {
- main: '#E0E0E0',
- text: '#666666',
- border: '#A2A8A2',
- },
- background: {
- canvas: '#F4F4F4',
- primary: '#E9E9E9',
- secondary: '#D8D8D8',
- elevated: '#E9E9E9',
- },
- action: {
- hover: '#D1D1D1',
- selected: '#B8B8B8',
- selectedBorder: '#88B88B',
- hoverOpacity: 0.1,
- focus: '#D1D1D1',
- disabledBackground: '#E0E0E0',
- disabledText: '#B8B8B8',
- disabledOpacity: 0.5,
- },
- gradients: {
- brandHorizontal: 'linear-gradient(270deg, #88B88B 0%, #6D8E6D 100%)',
- brandVertical: 'linear-gradient(0.01deg, #88B88B 0.01%, #6D8E6D 99.99%)',
- },
- contrastThreshold: 3,
- hoverFactor: 0.03,
- tonalOffset: 0.2,
- },
-};
-
-export default zenTheme;
diff --git a/packages/grafana-data/src/themes/types.ts b/packages/grafana-data/src/themes/types.ts
index f586937cf3c..d77c53062d3 100644
--- a/packages/grafana-data/src/themes/types.ts
+++ b/packages/grafana-data/src/themes/types.ts
@@ -1,3 +1,5 @@
+import { z } from 'zod';
+
import { GrafanaTheme } from '../types/theme';
import { ThemeBreakpoints } from './breakpoints';
@@ -35,27 +37,36 @@ export interface GrafanaTheme2 {
flags: {};
}
-/** @alpha */
-export interface ThemeRichColor {
+export const ThemeRichColorInputSchema = z.object({
/** color intent (primary, secondary, info, error, etc) */
- name: string;
+ name: z.string().optional(),
/** Main color */
- main: string;
+ main: z.string().optional(),
/** Used for hover */
- shade: string;
+ shade: z.string().optional(),
/** Used for text */
- text: string;
+ text: z.string().optional(),
/** Used for borders */
- border: string;
+ border: z.string().optional(),
/** Used subtly colored backgrounds */
- transparent: string;
+ transparent: z.string().optional(),
/** Used for weak colored borders like larger alert/banner boxes and smaller badges and tags */
- borderTransparent: string;
+ borderTransparent: z.string().optional(),
/** Text color for text ontop of main */
- contrastText: string;
-}
+ contrastText: z.string().optional(),
+});
+
+export const ThemeRichColorSchema = ThemeRichColorInputSchema.required();
+
+/** @alpha */
+export type ThemeRichColor = z.infer;
/** @internal */
export type DeepPartial = {
[P in keyof T]?: DeepPartial;
};
+
+/** @internal */
+export type DeepRequired = Required<{
+ [P in keyof T]: T[P] extends Required ? T[P] : DeepRequired;
+}>;
diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts
index b2d3c16a3b1..922d9273699 100644
--- a/packages/grafana-data/src/types/config.ts
+++ b/packages/grafana-data/src/types/config.ts
@@ -32,6 +32,7 @@ export type AppPluginConfig = {
path: string;
version: string;
preload: boolean;
+ /** @deprecated it will be removed in a future release */
angular: AngularMeta;
loadingStrategy: PluginLoadingStrategy;
dependencies: PluginDependencies;
@@ -219,6 +220,7 @@ export interface GrafanaConfig {
snapshotEnabled: boolean;
datasources: { [str: string]: DataSourceInstanceSettings };
panels: { [key: string]: PanelPluginMeta };
+ /** @deprecated it will be removed in a future release */
apps: Record;
auth: AuthSettings;
minRefreshInterval: string;
diff --git a/packages/grafana-data/src/types/dataLink.ts b/packages/grafana-data/src/types/dataLink.ts
index 815b67f0352..ad556a75c76 100644
--- a/packages/grafana-data/src/types/dataLink.ts
+++ b/packages/grafana-data/src/types/dataLink.ts
@@ -1,5 +1,6 @@
import { ScopedVars } from './ScopedVars';
import { ExploreCorrelationHelperData, ExplorePanelsState } from './explore';
+import { LinkTarget } from './linkTarget';
import { InterpolateFunction } from './panel';
import { DataQuery } from './query';
import { TimeRange } from './time';
@@ -88,8 +89,6 @@ export interface InternalDataLink {
range?: TimeRange;
}
-export type LinkTarget = '_blank' | '_self' | undefined;
-
/**
* Processed Link Model. The values are ready to use
*/
diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts
index 3982bdc33e5..eed0d330481 100644
--- a/packages/grafana-data/src/types/featureToggles.gen.ts
+++ b/packages/grafana-data/src/types/featureToggles.gen.ts
@@ -356,7 +356,7 @@ export interface FeatureToggles {
*/
dashboardScene?: boolean;
/**
- * Enables experimental new dashboard layouts
+ * Enables new dashboard layouts
*/
dashboardNewLayouts?: boolean;
/**
@@ -527,14 +527,14 @@ export interface FeatureToggles {
*/
dashboardTemplates?: boolean;
/**
- * Sets the logs table as default visualisation in logs explore
- */
- logsExploreTableDefaultVisualization?: boolean;
- /**
* Enables the new alert list view design
*/
alertingListViewV2?: boolean;
/**
+ * Enables the new Alerting navigation structure with improved menu grouping
+ */
+ alertingNavigationV2?: boolean;
+ /**
* Enables saved searches for alert rules list
*/
alertingSavedSearches?: boolean;
@@ -626,10 +626,6 @@ export interface FeatureToggles {
*/
exploreLogsAggregatedMetrics?: boolean;
/**
- * Used in Logs Drilldown to limit the time range
- */
- exploreLogsLimitedTimeRange?: boolean;
- /**
* Enables the gRPC client to authenticate with the App Platform by using ID & access tokens
*/
appPlatformGrpcClientAuth?: boolean;
@@ -657,14 +653,6 @@ export interface FeatureToggles {
*/
rolePickerDrawer?: boolean;
/**
- * Enable unified storage search
- */
- unifiedStorageSearch?: boolean;
- /**
- * Enable sprinkles on unified storage search
- */
- unifiedStorageSearchSprinkles?: boolean;
- /**
* Pick the dual write mode from database configs
*/
managedDualWriter?: boolean;
@@ -703,10 +691,6 @@ export interface FeatureToggles {
*/
passwordlessMagicLinkAuthentication?: boolean;
/**
- * Display Related Logs in Grafana Metrics Drilldown
- */
- exploreMetricsRelatedLogs?: boolean;
- /**
* Adds support for quotes and special characters in label values for Prometheus queries
*/
prometheusSpecialCharsInLabelValues?: boolean;
@@ -1004,6 +988,11 @@ export interface FeatureToggles {
*/
recentlyViewedDashboards?: boolean;
/**
+ * A/A test for recently viewed dashboards feature
+ * @default false
+ */
+ experimentRecentlyViewedDashboards?: boolean;
+ /**
* Enable configuration of alert enrichments in Grafana Cloud.
* @default false
*/
@@ -1266,4 +1255,8 @@ export interface FeatureToggles {
* Enables profiles exemplars support in profiles drilldown
*/
profilesExemplars?: boolean;
+ /**
+ * Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods
+ */
+ alertingSyncDispatchTimer?: boolean;
}
diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts
index 34e672b66f5..3dc7215a2cf 100644
--- a/packages/grafana-data/src/types/icon.ts
+++ b/packages/grafana-data/src/types/icon.ts
@@ -52,6 +52,7 @@ export const availableIconsIndex = {
bookmark: true,
'book-open': true,
'brackets-curly': true,
+ brain: true,
'browser-alt': true,
bug: true,
building: true,
diff --git a/packages/grafana-data/src/types/linkTarget.ts b/packages/grafana-data/src/types/linkTarget.ts
new file mode 100644
index 00000000000..2cdd963da7a
--- /dev/null
+++ b/packages/grafana-data/src/types/linkTarget.ts
@@ -0,0 +1,4 @@
+/**
+ * Target for links - controls whether link opens in new tab or same tab
+ */
+export type LinkTarget = '_blank' | '_self' | undefined;
diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts
index f9ebb23fc07..815b9d04e2d 100644
--- a/packages/grafana-data/src/types/navModel.ts
+++ b/packages/grafana-data/src/types/navModel.ts
@@ -1,7 +1,7 @@
import { ComponentType } from 'react';
-import { LinkTarget } from './dataLink';
import { IconName } from './icon';
+import { LinkTarget } from './linkTarget';
export interface NavLinkDTO {
id?: string;
diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts
index acf1e36c905..b9d6491cf9b 100644
--- a/packages/grafana-data/src/types/panel.ts
+++ b/packages/grafana-data/src/types/panel.ts
@@ -11,6 +11,7 @@ import { DataFrame } from './dataFrame';
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
import { FieldConfigSource } from './fieldOverrides';
import { IconName } from './icon';
+import { LinkTarget } from './linkTarget';
import { OptionEditorConfig } from './options';
import { PluginMeta } from './plugin';
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
@@ -191,6 +192,7 @@ export interface PanelMenuItem {
onClick?: (event: React.MouseEvent) => void;
shortcut?: string;
href?: string;
+ target?: LinkTarget;
subMenu?: PanelMenuItem[];
}
diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts
index 045dfdcee0b..8b96ac8f70f 100644
--- a/packages/grafana-data/src/types/plugin.ts
+++ b/packages/grafana-data/src/types/plugin.ts
@@ -53,6 +53,7 @@ export interface PluginError {
pluginType?: PluginType;
}
+/** @deprecated it will be removed in a future release */
export interface AngularMeta {
detected: boolean;
hideDeprecation: boolean;
diff --git a/packages/grafana-data/src/unstable.ts b/packages/grafana-data/src/unstable.ts
index 8a42447206f..43c2ff3071f 100644
--- a/packages/grafana-data/src/unstable.ts
+++ b/packages/grafana-data/src/unstable.ts
@@ -9,5 +9,4 @@
* and be subject to the standard policies
*/
-// This is a dummy export so typescript doesn't error importing an "empty module"
-export const unstable = {};
+export {};
diff --git a/packages/grafana-data/tsconfig.json b/packages/grafana-data/tsconfig.json
index 8e6013e32d9..3513caf9127 100644
--- a/packages/grafana-data/tsconfig.json
+++ b/packages/grafana-data/tsconfig.json
@@ -8,7 +8,8 @@
"emitDeclarationOnly": true,
"isolatedModules": true,
"rootDirs": ["."],
- "moduleResolution": "bundler"
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true
},
"exclude": ["dist/**/*"],
"include": [
diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json
index 36a9faa541b..93dd9837897 100644
--- a/packages/grafana-i18n/package.json
+++ b/packages/grafana-i18n/package.json
@@ -29,7 +29,6 @@
"@grafana-app/source": "./src/internal/index.ts"
},
"./eslint-plugin": {
- "@grafana-app/source": "./src/eslint/index.cjs",
"types": "./src/eslint/index.d.ts",
"default": "./src/eslint/index.cjs"
}
diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts
index 18cce14f236..99809235cab 100644
--- a/packages/grafana-runtime/src/config.ts
+++ b/packages/grafana-runtime/src/config.ts
@@ -86,6 +86,7 @@ export class GrafanaBootConfig {
snapshotEnabled = true;
datasources: { [str: string]: DataSourceInstanceSettings } = {};
panels: { [key: string]: PanelPluginMeta } = {};
+ /** @deprecated it will be removed in a future release, use isAppPluginInstalled or getAppPluginVersion instead */
apps: Record = {};
auth: AuthSettings = {};
minRefreshInterval = '';
diff --git a/packages/grafana-runtime/src/index.ts b/packages/grafana-runtime/src/index.ts
index 58b30be8542..380b87fee7d 100644
--- a/packages/grafana-runtime/src/index.ts
+++ b/packages/grafana-runtime/src/index.ts
@@ -77,3 +77,5 @@ export {
getCorrelationsService,
setCorrelationsService,
} from './services/CorrelationsService';
+export { getAppPluginVersion, isAppPluginInstalled } from './services/pluginMeta/apps';
+export { useAppPluginInstalled, useAppPluginVersion } from './services/pluginMeta/hooks';
diff --git a/packages/grafana-runtime/src/internal/index.ts b/packages/grafana-runtime/src/internal/index.ts
index aed6b86ebfb..fa13873c094 100644
--- a/packages/grafana-runtime/src/internal/index.ts
+++ b/packages/grafana-runtime/src/internal/index.ts
@@ -29,3 +29,5 @@ export {
export { UserStorage } from '../utils/userStorage';
export { initOpenFeature, evaluateBooleanFlag } from './openFeature';
+export { getAppPluginMeta, getAppPluginMetas, setAppPluginMetas } from '../services/pluginMeta/apps';
+export { useAppPluginMeta, useAppPluginMetas } from '../services/pluginMeta/hooks';
diff --git a/packages/grafana-runtime/src/services/pluginMeta/apps.test.ts b/packages/grafana-runtime/src/services/pluginMeta/apps.test.ts
new file mode 100644
index 00000000000..554917041cc
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/apps.test.ts
@@ -0,0 +1,258 @@
+import { evaluateBooleanFlag } from '../../internal/openFeature';
+
+import {
+ getAppPluginMeta,
+ getAppPluginMetas,
+ getAppPluginVersion,
+ isAppPluginInstalled,
+ setAppPluginMetas,
+} from './apps';
+import { initPluginMetas } from './plugins';
+import { app } from './test-fixtures/config.apps';
+
+jest.mock('./plugins', () => ({ ...jest.requireActual('./plugins'), initPluginMetas: jest.fn() }));
+jest.mock('../../internal/openFeature', () => ({
+ ...jest.requireActual('../../internal/openFeature'),
+ evaluateBooleanFlag: jest.fn(),
+}));
+
+const initPluginMetasMock = jest.mocked(initPluginMetas);
+const evaluateBooleanFlagMock = jest.mocked(evaluateBooleanFlag);
+
+describe('when useMTPlugins flag is enabled and apps is not initialized', () => {
+ beforeEach(() => {
+ setAppPluginMetas({});
+ jest.resetAllMocks();
+ initPluginMetasMock.mockResolvedValue({ items: [] });
+ evaluateBooleanFlagMock.mockReturnValue(true);
+ });
+
+ it('getAppPluginMetas should call initPluginMetas and return correct result', async () => {
+ const apps = await getAppPluginMetas();
+
+ expect(apps).toEqual([]);
+ expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('getAppPluginMeta should call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginMeta('myorg-someplugin-app');
+
+ expect(result).toEqual(null);
+ expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('isAppPluginInstalled should call initPluginMetas and return false', async () => {
+ const installed = await isAppPluginInstalled('myorg-someplugin-app');
+
+ expect(installed).toEqual(false);
+ expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('getAppPluginVersion should call initPluginMetas and return null', async () => {
+ const result = await getAppPluginVersion('myorg-someplugin-app');
+
+ expect(result).toEqual(null);
+ expect(initPluginMetasMock).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe('when useMTPlugins flag is enabled and apps is initialized', () => {
+ beforeEach(() => {
+ setAppPluginMetas({ 'myorg-someplugin-app': app });
+ jest.resetAllMocks();
+ evaluateBooleanFlagMock.mockReturnValue(true);
+ });
+
+ it('getAppPluginMetas should not call initPluginMetas and return correct result', async () => {
+ const apps = await getAppPluginMetas();
+
+ expect(apps).toEqual([app]);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginMeta should not call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginMeta('myorg-someplugin-app');
+
+ expect(result).toEqual(app);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginMeta should return null if the pluginId is not found', async () => {
+ const result = await getAppPluginMeta('otherorg-otherplugin-app');
+
+ expect(result).toEqual(null);
+ });
+
+ it('isAppPluginInstalled should not call initPluginMetas and return true', async () => {
+ const installed = await isAppPluginInstalled('myorg-someplugin-app');
+
+ expect(installed).toEqual(true);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('isAppPluginInstalled should return false if the pluginId is not found', async () => {
+ const result = await isAppPluginInstalled('otherorg-otherplugin-app');
+
+ expect(result).toEqual(false);
+ });
+
+ it('getAppPluginVersion should not call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginVersion('myorg-someplugin-app');
+
+ expect(result).toEqual('1.0.0');
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginVersion should return null if the pluginId is not found', async () => {
+ const result = await getAppPluginVersion('otherorg-otherplugin-app');
+
+ expect(result).toEqual(null);
+ });
+});
+
+describe('when useMTPlugins flag is disabled and apps is not initialized', () => {
+ beforeEach(() => {
+ setAppPluginMetas({});
+ jest.resetAllMocks();
+ evaluateBooleanFlagMock.mockReturnValue(false);
+ });
+
+ it('getAppPluginMetas should not call initPluginMetas and return correct result', async () => {
+ const apps = await getAppPluginMetas();
+
+ expect(apps).toEqual([]);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginMeta should not call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginMeta('myorg-someplugin-app');
+
+ expect(result).toEqual(null);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('isAppPluginInstalled should not call initPluginMetas and return false', async () => {
+ const result = await isAppPluginInstalled('myorg-someplugin-app');
+
+ expect(result).toEqual(false);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginVersion should not call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginVersion('myorg-someplugin-app');
+
+ expect(result).toEqual(null);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+});
+
+describe('when useMTPlugins flag is disabled and apps is initialized', () => {
+ beforeEach(() => {
+ setAppPluginMetas({ 'myorg-someplugin-app': app });
+ jest.resetAllMocks();
+ evaluateBooleanFlagMock.mockReturnValue(false);
+ });
+
+ it('getAppPluginMetas should not call initPluginMetas and return correct result', async () => {
+ const apps = await getAppPluginMetas();
+
+ expect(apps).toEqual([app]);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginMeta should not call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginMeta('myorg-someplugin-app');
+
+ expect(result).toEqual(app);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginMeta should return null if the pluginId is not found', async () => {
+ const result = await getAppPluginMeta('otherorg-otherplugin-app');
+
+ expect(result).toEqual(null);
+ });
+
+ it('isAppPluginInstalled should not call initPluginMetas and return true', async () => {
+ const result = await isAppPluginInstalled('myorg-someplugin-app');
+
+ expect(result).toEqual(true);
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('isAppPluginInstalled should return false if the pluginId is not found', async () => {
+ const result = await isAppPluginInstalled('otherorg-otherplugin-app');
+
+ expect(result).toEqual(false);
+ });
+
+ it('getAppPluginVersion should not call initPluginMetas and return correct result', async () => {
+ const result = await getAppPluginVersion('myorg-someplugin-app');
+
+ expect(result).toEqual('1.0.0');
+ expect(initPluginMetasMock).not.toHaveBeenCalled();
+ });
+
+ it('getAppPluginVersion should return null if the pluginId is not found', async () => {
+ const result = await getAppPluginVersion('otherorg-otherplugin-app');
+
+ expect(result).toEqual(null);
+ });
+});
+
+describe('immutability', () => {
+ beforeEach(() => {
+ setAppPluginMetas({ 'myorg-someplugin-app': app });
+ jest.resetAllMocks();
+ evaluateBooleanFlagMock.mockReturnValue(false);
+ });
+
+ it('getAppPluginMetas should return a deep clone', async () => {
+ const mutatedApps = await getAppPluginMetas();
+
+ // assert we have correct props
+ expect(mutatedApps).toHaveLength(1);
+ expect(mutatedApps[0].dependencies.grafanaDependency).toEqual('>=10.4.0');
+ expect(mutatedApps[0].extensions.addedLinks).toHaveLength(0);
+
+ // mutate deep props
+ mutatedApps[0].dependencies.grafanaDependency = '';
+ mutatedApps[0].extensions.addedLinks.push({ targets: [], title: '', description: '' });
+
+ // assert we have mutated props
+ expect(mutatedApps[0].dependencies.grafanaDependency).toEqual('');
+ expect(mutatedApps[0].extensions.addedLinks).toHaveLength(1);
+ expect(mutatedApps[0].extensions.addedLinks[0]).toEqual({ targets: [], title: '', description: '' });
+
+ const apps = await getAppPluginMetas();
+
+ // assert that we have not mutated the source
+ expect(apps[0].dependencies.grafanaDependency).toEqual('>=10.4.0');
+ expect(apps[0].extensions.addedLinks).toHaveLength(0);
+ });
+
+ it('getAppPluginMeta should return a deep clone', async () => {
+ const mutatedApp = await getAppPluginMeta('myorg-someplugin-app');
+
+ // assert we have correct props
+ expect(mutatedApp).toBeDefined();
+ expect(mutatedApp!.dependencies.grafanaDependency).toEqual('>=10.4.0');
+ expect(mutatedApp!.extensions.addedLinks).toHaveLength(0);
+
+ // mutate deep props
+ mutatedApp!.dependencies.grafanaDependency = '';
+ mutatedApp!.extensions.addedLinks.push({ targets: [], title: '', description: '' });
+
+ // assert we have mutated props
+ expect(mutatedApp!.dependencies.grafanaDependency).toEqual('');
+ expect(mutatedApp!.extensions.addedLinks).toHaveLength(1);
+ expect(mutatedApp!.extensions.addedLinks[0]).toEqual({ targets: [], title: '', description: '' });
+
+ const result = await getAppPluginMeta('myorg-someplugin-app');
+
+ // assert that we have not mutated the source
+ expect(result).toBeDefined();
+ expect(result!.dependencies.grafanaDependency).toEqual('>=10.4.0');
+ expect(result!.extensions.addedLinks).toHaveLength(0);
+ });
+});
diff --git a/packages/grafana-runtime/src/services/pluginMeta/apps.ts b/packages/grafana-runtime/src/services/pluginMeta/apps.ts
new file mode 100644
index 00000000000..7db359b5a4b
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/apps.ts
@@ -0,0 +1,71 @@
+import type { AppPluginConfig } from '@grafana/data';
+
+import { config } from '../../config';
+import { evaluateBooleanFlag } from '../../internal/openFeature';
+
+import { getAppPluginMapper } from './mappers/mappers';
+import { initPluginMetas } from './plugins';
+import type { AppPluginMetas } from './types';
+
+let apps: AppPluginMetas = {};
+
+function initialized(): boolean {
+ return Boolean(Object.keys(apps).length);
+}
+
+async function initAppPluginMetas(): Promise {
+ if (!evaluateBooleanFlag('useMTPlugins', false)) {
+ // eslint-disable-next-line no-restricted-syntax
+ apps = config.apps;
+ return;
+ }
+
+ const metas = await initPluginMetas();
+ const mapper = getAppPluginMapper();
+ apps = mapper(metas);
+}
+
+export async function getAppPluginMetas(): Promise {
+ if (!initialized()) {
+ await initAppPluginMetas();
+ }
+
+ return Object.values(structuredClone(apps));
+}
+
+export async function getAppPluginMeta(pluginId: string): Promise {
+ if (!initialized()) {
+ await initAppPluginMetas();
+ }
+
+ const app = apps[pluginId];
+ return app ? structuredClone(app) : null;
+}
+
+/**
+ * Check if an app plugin is installed. The function does not check if the app plugin is enabled.
+ * @param pluginId - The id of the app plugin.
+ * @returns True if the app plugin is installed, false otherwise.
+ */
+export async function isAppPluginInstalled(pluginId: string): Promise {
+ const app = await getAppPluginMeta(pluginId);
+ return Boolean(app);
+}
+
+/**
+ * Get the version of an app plugin.
+ * @param pluginId - The id of the app plugin.
+ * @returns The version of the app plugin, or null if the plugin is not installed.
+ */
+export async function getAppPluginVersion(pluginId: string): Promise {
+ const app = await getAppPluginMeta(pluginId);
+ return app?.version ?? null;
+}
+
+export function setAppPluginMetas(override: AppPluginMetas): void {
+ if (process.env.NODE_ENV !== 'test') {
+ throw new Error('setAppPluginMetas() function can only be called from tests.');
+ }
+
+ apps = structuredClone(override);
+}
diff --git a/packages/grafana-runtime/src/services/pluginMeta/hooks.test.tsx b/packages/grafana-runtime/src/services/pluginMeta/hooks.test.tsx
new file mode 100644
index 00000000000..1e3c7311118
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/hooks.test.tsx
@@ -0,0 +1,214 @@
+import { renderHook, waitFor } from '@testing-library/react';
+
+import {
+ getAppPluginMeta,
+ getAppPluginMetas,
+ getAppPluginVersion,
+ isAppPluginInstalled,
+ setAppPluginMetas,
+} from './apps';
+import { useAppPluginMeta, useAppPluginMetas, useAppPluginInstalled, useAppPluginVersion } from './hooks';
+import { apps } from './test-fixtures/config.apps';
+
+const actualApps = jest.requireActual('./apps');
+jest.mock('./apps', () => ({
+ ...jest.requireActual('./apps'),
+ getAppPluginMetas: jest.fn(),
+ getAppPluginMeta: jest.fn(),
+ isAppPluginInstalled: jest.fn(),
+ getAppPluginVersion: jest.fn(),
+}));
+const getAppPluginMetaMock = jest.mocked(getAppPluginMeta);
+const getAppPluginMetasMock = jest.mocked(getAppPluginMetas);
+const isAppPluginInstalledMock = jest.mocked(isAppPluginInstalled);
+const getAppPluginVersionMock = jest.mocked(getAppPluginVersion);
+
+describe('useAppPluginMeta', () => {
+ beforeEach(() => {
+ setAppPluginMetas(apps);
+ jest.resetAllMocks();
+ getAppPluginMetaMock.mockImplementation(actualApps.getAppPluginMeta);
+ });
+
+ it('should return correct default values', async () => {
+ const { result } = renderHook(() => useAppPluginMeta('grafana-exploretraces-app'));
+
+ expect(result.current.loading).toEqual(true);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toBeUndefined();
+
+ await waitFor(() => expect(result.current.loading).toEqual(true));
+ });
+
+ it('should return correct values after loading', async () => {
+ const { result } = renderHook(() => useAppPluginMeta('grafana-exploretraces-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual(apps['grafana-exploretraces-app']);
+ });
+
+ it('should return correct values if the pluginId does not exist', async () => {
+ const { result } = renderHook(() => useAppPluginMeta('otherorg-otherplugin-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual(null);
+ });
+
+ it('should return correct values if useAppPluginMeta throws', async () => {
+ getAppPluginMetaMock.mockRejectedValue(new Error('Some error'));
+
+ const { result } = renderHook(() => useAppPluginMeta('otherorg-otherplugin-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toEqual(new Error('Some error'));
+ expect(result.current.value).toBeUndefined();
+ });
+});
+
+describe('useAppPluginMetas', () => {
+ beforeEach(() => {
+ setAppPluginMetas(apps);
+ jest.resetAllMocks();
+ getAppPluginMetasMock.mockImplementation(actualApps.getAppPluginMetas);
+ });
+
+ it('should return correct default values', async () => {
+ const { result } = renderHook(() => useAppPluginMetas());
+
+ expect(result.current.loading).toEqual(true);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toBeUndefined();
+
+ await waitFor(() => expect(result.current.loading).toEqual(true));
+ });
+
+ it('should return correct values after loading', async () => {
+ const { result } = renderHook(() => useAppPluginMetas());
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual(Object.values(apps));
+ });
+
+ it('should return correct values if useAppPluginMetas throws', async () => {
+ getAppPluginMetasMock.mockRejectedValue(new Error('Some error'));
+
+ const { result } = renderHook(() => useAppPluginMetas());
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toEqual(new Error('Some error'));
+ expect(result.current.value).toBeUndefined();
+ });
+});
+
+describe('useAppPluginInstalled', () => {
+ beforeEach(() => {
+ setAppPluginMetas(apps);
+ jest.resetAllMocks();
+ isAppPluginInstalledMock.mockImplementation(actualApps.isAppPluginInstalled);
+ });
+
+ it('should return correct default values', async () => {
+ const { result } = renderHook(() => useAppPluginInstalled('grafana-exploretraces-app'));
+
+ expect(result.current.loading).toEqual(true);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toBeUndefined();
+
+ await waitFor(() => expect(result.current.loading).toEqual(true));
+ });
+
+ it('should return correct values after loading', async () => {
+ const { result } = renderHook(() => useAppPluginInstalled('grafana-exploretraces-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual(true);
+ });
+
+ it('should return correct values if the pluginId does not exist', async () => {
+ const { result } = renderHook(() => useAppPluginInstalled('otherorg-otherplugin-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual(false);
+ });
+
+ it('should return correct values if isAppPluginInstalled throws', async () => {
+ isAppPluginInstalledMock.mockRejectedValue(new Error('Some error'));
+
+ const { result } = renderHook(() => useAppPluginInstalled('otherorg-otherplugin-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toEqual(new Error('Some error'));
+ expect(result.current.value).toBeUndefined();
+ });
+});
+
+describe('useAppPluginVersion', () => {
+ beforeEach(() => {
+ setAppPluginMetas(apps);
+ jest.resetAllMocks();
+ getAppPluginVersionMock.mockImplementation(actualApps.getAppPluginVersion);
+ });
+
+ it('should return correct default values', async () => {
+ const { result } = renderHook(() => useAppPluginVersion('grafana-exploretraces-app'));
+
+ expect(result.current.loading).toEqual(true);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toBeUndefined();
+
+ await waitFor(() => expect(result.current.loading).toEqual(true));
+ });
+
+ it('should return correct values after loading', async () => {
+ const { result } = renderHook(() => useAppPluginVersion('grafana-exploretraces-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual('1.2.2');
+ });
+
+ it('should return correct values if the pluginId does not exist', async () => {
+ const { result } = renderHook(() => useAppPluginVersion('otherorg-otherplugin-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.value).toEqual(null);
+ });
+
+ it('should return correct values if getAppPluginVersion throws', async () => {
+ getAppPluginVersionMock.mockRejectedValue(new Error('Some error'));
+
+ const { result } = renderHook(() => useAppPluginVersion('otherorg-otherplugin-app'));
+
+ await waitFor(() => expect(result.current.loading).toEqual(false));
+
+ expect(result.current.loading).toEqual(false);
+ expect(result.current.error).toEqual(new Error('Some error'));
+ expect(result.current.value).toBeUndefined();
+ });
+});
diff --git a/packages/grafana-runtime/src/services/pluginMeta/hooks.tsx b/packages/grafana-runtime/src/services/pluginMeta/hooks.tsx
new file mode 100644
index 00000000000..58ac42bbdd2
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/hooks.tsx
@@ -0,0 +1,35 @@
+import { useAsync } from 'react-use';
+
+import { getAppPluginMeta, getAppPluginMetas, getAppPluginVersion, isAppPluginInstalled } from './apps';
+
+export function useAppPluginMetas() {
+ const { loading, error, value } = useAsync(async () => getAppPluginMetas());
+ return { loading, error, value };
+}
+
+export function useAppPluginMeta(pluginId: string) {
+ const { loading, error, value } = useAsync(async () => getAppPluginMeta(pluginId));
+ return { loading, error, value };
+}
+
+/**
+ * Hook that checks if an app plugin is installed. The hook does not check if the app plugin is enabled.
+ * @param pluginId - The ID of the app plugin.
+ * @returns loading, error, value of the app plugin installed status.
+ * The value is true if the app plugin is installed, false otherwise.
+ */
+export function useAppPluginInstalled(pluginId: string) {
+ const { loading, error, value } = useAsync(async () => isAppPluginInstalled(pluginId));
+ return { loading, error, value };
+}
+
+/**
+ * Hook that gets the version of an app plugin.
+ * @param pluginId - The ID of the app plugin.
+ * @returns loading, error, value of the app plugin version.
+ * The value is the version of the app plugin, or null if the plugin is not installed.
+ */
+export function useAppPluginVersion(pluginId: string) {
+ const { loading, error, value } = useAsync(async () => getAppPluginVersion(pluginId));
+ return { loading, error, value };
+}
diff --git a/packages/grafana-runtime/src/services/pluginMeta/mappers/mappers.ts b/packages/grafana-runtime/src/services/pluginMeta/mappers/mappers.ts
new file mode 100644
index 00000000000..15505b2edc0
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/mappers/mappers.ts
@@ -0,0 +1,7 @@
+import { AppPluginMetasMapper, PluginMetasResponse } from '../types';
+
+import { v0alpha1AppMapper } from './v0alpha1AppMapper';
+
+export function getAppPluginMapper(): AppPluginMetasMapper {
+ return v0alpha1AppMapper;
+}
diff --git a/packages/grafana-runtime/src/services/pluginMeta/mappers/v0alpha1AppMapper.test.ts b/packages/grafana-runtime/src/services/pluginMeta/mappers/v0alpha1AppMapper.test.ts
new file mode 100644
index 00000000000..dfc82d41b3e
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/mappers/v0alpha1AppMapper.test.ts
@@ -0,0 +1,84 @@
+import { apps } from '../test-fixtures/config.apps';
+import { v0alpha1Response } from '../test-fixtures/v0alpha1Response';
+
+import { v0alpha1AppMapper } from './v0alpha1AppMapper';
+
+const PLUGIN_IDS = v0alpha1Response.items
+ .filter((i) => i.spec.pluginJson.type === 'app')
+ .map((i) => ({ pluginId: i.spec.pluginJson.id }));
+
+describe('v0alpha1AppMapper', () => {
+ describe.each(PLUGIN_IDS)('when called for pluginId:$pluginId', ({ pluginId }) => {
+ it('should map id property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].id).toEqual(apps[pluginId].id);
+ });
+
+ it('should map path property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].path).toEqual(apps[pluginId].path);
+ });
+
+ it('should map version property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].version).toEqual(apps[pluginId].version);
+ });
+
+ it('should map preload property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].preload).toEqual(apps[pluginId].preload);
+ });
+
+ it('should map angular property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].angular).toEqual({});
+ });
+
+ it('should map loadingStrategy property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].loadingStrategy).toEqual(apps[pluginId].loadingStrategy);
+ });
+
+ it('should map dependencies property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].dependencies).toEqual(apps[pluginId].dependencies);
+ });
+
+ it('should map extensions property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].extensions.addedComponents).toEqual(apps[pluginId].extensions.addedComponents);
+ expect(result[pluginId].extensions.addedFunctions).toEqual(apps[pluginId].extensions.addedFunctions);
+ expect(result[pluginId].extensions.addedLinks).toEqual(apps[pluginId].extensions.addedLinks);
+ expect(result[pluginId].extensions.exposedComponents).toEqual(apps[pluginId].extensions.exposedComponents);
+ expect(result[pluginId].extensions.extensionPoints).toEqual(apps[pluginId].extensions.extensionPoints);
+ });
+
+ it('should map moduleHash property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].moduleHash).toEqual(apps[pluginId].moduleHash);
+ });
+
+ it('should map buildMode property correctly', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(result[pluginId].buildMode).toEqual(apps[pluginId].buildMode);
+ });
+ });
+
+ it('should only map specs with type app', () => {
+ const result = v0alpha1AppMapper(v0alpha1Response);
+
+ expect(v0alpha1Response.items).toHaveLength(58);
+ expect(Object.keys(result)).toHaveLength(5);
+ expect(Object.keys(result)).toEqual(Object.keys(apps));
+ });
+});
diff --git a/packages/grafana-runtime/src/services/pluginMeta/mappers/v0alpha1AppMapper.ts b/packages/grafana-runtime/src/services/pluginMeta/mappers/v0alpha1AppMapper.ts
new file mode 100644
index 00000000000..aa5ca6e2ce0
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/mappers/v0alpha1AppMapper.ts
@@ -0,0 +1,111 @@
+import {
+ type AngularMeta,
+ type AppPluginConfig,
+ type PluginDependencies,
+ type PluginExtensions,
+ PluginLoadingStrategy,
+ type PluginType,
+} from '@grafana/data';
+
+import type { AppPluginMetas, AppPluginMetasMapper, PluginMetasResponse } from '../types';
+import type { Spec as v0alpha1Spec } from '../types/types.spec.gen';
+
+function angularyMapper(spec: v0alpha1Spec): AngularMeta {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ return {} as AngularMeta;
+}
+
+function dependenciesMapper(spec: v0alpha1Spec): PluginDependencies {
+ const plugins = (spec.pluginJson.dependencies?.plugins ?? []).map((v) => ({
+ ...v,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ type: v.type as PluginType,
+ version: '',
+ }));
+
+ const dependencies: PluginDependencies = {
+ ...spec.pluginJson.dependencies,
+ extensions: {
+ exposedComponents: spec.pluginJson.dependencies.extensions?.exposedComponents ?? [],
+ },
+ grafanaDependency: spec.pluginJson.dependencies.grafanaDependency,
+ grafanaVersion: spec.pluginJson.dependencies.grafanaVersion ?? '',
+ plugins,
+ };
+
+ return dependencies;
+}
+
+function extensionsMapper(spec: v0alpha1Spec): PluginExtensions {
+ const addedComponents = spec.pluginJson.extensions?.addedComponents ?? [];
+ const addedFunctions = spec.pluginJson.extensions?.addedFunctions ?? [];
+ const addedLinks = spec.pluginJson.extensions?.addedLinks ?? [];
+ const exposedComponents = (spec.pluginJson.extensions?.exposedComponents ?? []).map((v) => ({
+ ...v,
+ description: v.description ?? '',
+ title: v.title ?? '',
+ }));
+ const extensionPoints = (spec.pluginJson.extensions?.extensionPoints ?? []).map((v) => ({
+ ...v,
+ description: v.description ?? '',
+ title: v.title ?? '',
+ }));
+
+ const extensions: PluginExtensions = {
+ addedComponents,
+ addedFunctions,
+ addedLinks,
+ exposedComponents,
+ extensionPoints,
+ };
+
+ return extensions;
+}
+
+function loadingStrategyMapper(spec: v0alpha1Spec): PluginLoadingStrategy {
+ const loadingStrategy = spec.module?.loadingStrategy ?? PluginLoadingStrategy.fetch;
+ if (loadingStrategy === PluginLoadingStrategy.script) {
+ return PluginLoadingStrategy.script;
+ }
+
+ return PluginLoadingStrategy.fetch;
+}
+
+function specMapper(spec: v0alpha1Spec): AppPluginConfig {
+ const { id, info, preload = false } = spec.pluginJson;
+ const angular = angularyMapper(spec);
+ const dependencies = dependenciesMapper(spec);
+ const extensions = extensionsMapper(spec);
+ const loadingStrategy = loadingStrategyMapper(spec);
+ const path = spec.module?.path ?? '';
+ const version = info.version;
+ const buildMode = spec.pluginJson.buildMode ?? 'production';
+ const moduleHash = spec.module?.hash;
+
+ return {
+ id,
+ angular,
+ dependencies,
+ extensions,
+ loadingStrategy,
+ path,
+ preload,
+ version,
+ buildMode,
+ moduleHash,
+ };
+}
+
+export const v0alpha1AppMapper: AppPluginMetasMapper = (response) => {
+ const result: AppPluginMetas = {};
+
+ return response.items.reduce((acc, curr) => {
+ if (curr.spec.pluginJson.type !== 'app') {
+ return acc;
+ }
+
+ const config = specMapper(curr.spec);
+ acc[config.id] = config;
+ return acc;
+ }, result);
+};
diff --git a/packages/grafana-runtime/src/services/pluginMeta/plugins.test.ts b/packages/grafana-runtime/src/services/pluginMeta/plugins.test.ts
new file mode 100644
index 00000000000..9a5077d1b2b
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/plugins.test.ts
@@ -0,0 +1,153 @@
+import { evaluateBooleanFlag } from '../../internal/openFeature';
+
+import { clearCache, initPluginMetas } from './plugins';
+import { v0alpha1Meta } from './test-fixtures/v0alpha1Response';
+
+jest.mock('../../internal/openFeature', () => ({
+ ...jest.requireActual('../../internal/openFeature'),
+ evaluateBooleanFlag: jest.fn(),
+}));
+
+const evaluateBooleanFlagMock = jest.mocked(evaluateBooleanFlag);
+
+describe('when useMTPlugins toggle is enabled and cache is not initialized', () => {
+ const originalFetch = global.fetch;
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ clearCache();
+ evaluateBooleanFlagMock.mockReturnValue(true);
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
+ it('initPluginMetas should call loadPluginMetas and return correct result if response is ok', async () => {
+ global.fetch = jest.fn().mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: () => Promise.resolve({ items: [v0alpha1Meta] }),
+ });
+
+ const response = await initPluginMetas();
+
+ expect(response.items).toHaveLength(1);
+ expect(response.items[0]).toEqual(v0alpha1Meta);
+ expect(global.fetch).toHaveBeenCalledTimes(1);
+ expect(global.fetch).toHaveBeenCalledWith('/apis/plugins.grafana.app/v0alpha1/namespaces/default/metas');
+ });
+
+ it('initPluginMetas should call loadPluginMetas and return correct result if response is not ok', async () => {
+ global.fetch = jest.fn().mockResolvedValue({
+ ok: false,
+ status: 404,
+ statusText: 'Not found',
+ });
+
+ await expect(initPluginMetas()).rejects.toThrow(new Error(`Failed to load plugin metas 404:Not found`));
+ expect(global.fetch).toHaveBeenCalledTimes(1);
+ expect(global.fetch).toHaveBeenCalledWith('/apis/plugins.grafana.app/v0alpha1/namespaces/default/metas');
+ });
+});
+
+describe('when useMTPlugins toggle is enabled and cache is initialized', () => {
+ const originalFetch = global.fetch;
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ clearCache();
+ evaluateBooleanFlagMock.mockReturnValue(true);
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
+ it('initPluginMetas should return cache', async () => {
+ global.fetch = jest.fn().mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: () => Promise.resolve({ items: [v0alpha1Meta] }),
+ });
+
+ const original = await initPluginMetas();
+ const cached = await initPluginMetas();
+
+ expect(original).toEqual(cached);
+ expect(global.fetch).toHaveBeenCalledTimes(1);
+ });
+
+ it('initPluginMetas should return inflight promise', async () => {
+ jest.useFakeTimers();
+
+ global.fetch = jest.fn().mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: () => Promise.resolve({ items: [v0alpha1Meta] }),
+ });
+
+ const original = initPluginMetas();
+ const cached = initPluginMetas();
+ await jest.runAllTimersAsync();
+
+ expect(original).toEqual(cached);
+ expect(global.fetch).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe('when useMTPlugins toggle is disabled and cache is not initialized', () => {
+ const originalFetch = global.fetch;
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ clearCache();
+ global.fetch = jest.fn();
+ evaluateBooleanFlagMock.mockReturnValue(false);
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
+ it('initPluginMetas should call loadPluginMetas and return correct result if response is ok', async () => {
+ const response = await initPluginMetas();
+
+ expect(response.items).toHaveLength(0);
+ expect(global.fetch).not.toHaveBeenCalled();
+ });
+});
+
+describe('when useMTPlugins toggle is disabled and cache is initialized', () => {
+ const originalFetch = global.fetch;
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ clearCache();
+ global.fetch = jest.fn();
+ evaluateBooleanFlagMock.mockReturnValue(false);
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
+ it('initPluginMetas should return cache', async () => {
+ const original = await initPluginMetas();
+ const cached = await initPluginMetas();
+
+ expect(original).toEqual(cached);
+ expect(global.fetch).not.toHaveBeenCalled();
+ });
+
+ it('initPluginMetas should return inflight promise', async () => {
+ jest.useFakeTimers();
+
+ const original = initPluginMetas();
+ const cached = initPluginMetas();
+ await jest.runAllTimersAsync();
+
+ expect(original).toEqual(cached);
+ expect(global.fetch).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/grafana-runtime/src/services/pluginMeta/plugins.ts b/packages/grafana-runtime/src/services/pluginMeta/plugins.ts
new file mode 100644
index 00000000000..ec2fa4a9d11
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/plugins.ts
@@ -0,0 +1,41 @@
+import { config } from '../../config';
+import { evaluateBooleanFlag } from '../../internal/openFeature';
+
+import type { PluginMetasResponse } from './types';
+
+let initPromise: Promise | null = null;
+
+function getApiVersion(): string {
+ return 'v0alpha1';
+}
+
+async function loadPluginMetas(): Promise {
+ if (!evaluateBooleanFlag('useMTPlugins', false)) {
+ const result = { items: [] };
+ return result;
+ }
+
+ const metas = await fetch(`/apis/plugins.grafana.app/${getApiVersion()}/namespaces/${config.namespace}/metas`);
+ if (!metas.ok) {
+ throw new Error(`Failed to load plugin metas ${metas.status}:${metas.statusText}`);
+ }
+
+ const result = await metas.json();
+ return result;
+}
+
+export function initPluginMetas(): Promise {
+ if (!initPromise) {
+ initPromise = loadPluginMetas();
+ }
+
+ return initPromise;
+}
+
+export function clearCache() {
+ if (process.env.NODE_ENV !== 'test') {
+ throw new Error('clearCache() function can only be called from tests.');
+ }
+
+ initPromise = null;
+}
diff --git a/packages/grafana-runtime/src/services/pluginMeta/test-fixtures/config.apps.ts b/packages/grafana-runtime/src/services/pluginMeta/test-fixtures/config.apps.ts
new file mode 100644
index 00000000000..365308bd76c
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/test-fixtures/config.apps.ts
@@ -0,0 +1,303 @@
+import { cloneDeep } from 'lodash';
+
+import { AngularMeta, AppPluginConfig, PluginLoadingStrategy } from '@grafana/data';
+
+import { AppPluginMetas } from '../types';
+
+export const app: AppPluginConfig = cloneDeep({
+ id: 'myorg-someplugin-app',
+ path: 'public/plugins/myorg-someplugin-app/module.js',
+ version: '1.0.0',
+ preload: false,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ angular: { detected: false } as AngularMeta,
+ loadingStrategy: PluginLoadingStrategy.script,
+ extensions: {
+ addedLinks: [],
+ addedComponents: [],
+ exposedComponents: [],
+ extensionPoints: [],
+ addedFunctions: [],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.4.0',
+ grafanaVersion: '*',
+ plugins: [],
+ extensions: {
+ exposedComponents: [],
+ },
+ },
+ buildMode: 'production',
+});
+
+export const apps: AppPluginMetas = cloneDeep({
+ 'grafana-exploretraces-app': {
+ id: 'grafana-exploretraces-app',
+ path: 'public/plugins/grafana-exploretraces-app/module.js',
+ version: '1.2.2',
+ preload: true,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ angular: { detected: false } as AngularMeta,
+ loadingStrategy: PluginLoadingStrategy.script,
+ extensions: {
+ addedLinks: [
+ {
+ targets: ['grafana/dashboard/panel/menu'],
+ title: 'Open in Traces Drilldown',
+ description: 'Open current query in the Traces Drilldown app',
+ },
+ {
+ targets: ['grafana/explore/toolbar/action'],
+ title: 'Open in Grafana Traces Drilldown',
+ description: 'Try our new queryless experience for traces',
+ },
+ ],
+ addedComponents: [
+ {
+ targets: ['grafana-asserts-app/entity-assertions-widget/v1'],
+ title: 'Asserts widget',
+ description: 'A block with assertions for a given service',
+ },
+ {
+ targets: ['grafana-asserts-app/insights-timeline-widget/v1'],
+ title: 'Insights Timeline Widget',
+ description: 'Widget for displaying insights timeline in other apps',
+ },
+ ],
+ exposedComponents: [
+ {
+ id: 'grafana-exploretraces-app/open-in-explore-traces-button/v1',
+ title: 'Open in Traces Drilldown button',
+ description: 'A button that opens a traces view in the Traces Drilldown app.',
+ },
+ {
+ id: 'grafana-exploretraces-app/embedded-trace-exploration/v1',
+ title: 'Embedded Trace Exploration',
+ description:
+ 'A component that renders a trace exploration view that can be embedded in other parts of Grafana.',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-exploretraces-app/investigation/v1',
+ title: '',
+ description: '',
+ },
+ {
+ id: 'grafana-exploretraces-app/get-logs-drilldown-link/v1',
+ title: '',
+ description: '',
+ },
+ ],
+ addedFunctions: [],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.5.0',
+ grafanaVersion: '*',
+ plugins: [],
+ extensions: {
+ exposedComponents: [
+ 'grafana-asserts-app/entity-assertions-widget/v1',
+ 'grafana-asserts-app/insights-timeline-widget/v1',
+ ],
+ },
+ },
+ buildMode: 'production',
+ },
+ 'grafana-lokiexplore-app': {
+ id: 'grafana-lokiexplore-app',
+ path: 'public/plugins/grafana-lokiexplore-app/module.js',
+ version: '1.0.32',
+ preload: true,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ angular: { detected: false } as AngularMeta,
+ loadingStrategy: PluginLoadingStrategy.script,
+ extensions: {
+ addedLinks: [
+ {
+ targets: [
+ 'grafana/dashboard/panel/menu',
+ 'grafana/explore/toolbar/action',
+ 'grafana-metricsdrilldown-app/open-in-logs-drilldown/v1',
+ 'grafana-assistant-app/navigateToDrilldown/v1',
+ ],
+ title: 'Open in Grafana Logs Drilldown',
+ description: 'Open current query in the Grafana Logs Drilldown view',
+ },
+ ],
+ addedComponents: [
+ {
+ targets: ['grafana-asserts-app/insights-timeline-widget/v1'],
+ title: 'Insights Timeline Widget',
+ description: 'Widget for displaying insights timeline in other apps',
+ },
+ ],
+ exposedComponents: [
+ {
+ id: 'grafana-lokiexplore-app/open-in-explore-logs-button/v1',
+ title: 'Open in Logs Drilldown button',
+ description: 'A button that opens a logs view in the Logs Drilldown app.',
+ },
+ {
+ id: 'grafana-lokiexplore-app/embedded-logs-exploration/v1',
+ title: 'Embedded Logs Exploration',
+ description:
+ 'A component that renders a logs exploration view that can be embedded in other parts of Grafana.',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-lokiexplore-app/investigation/v1',
+ title: '',
+ description: '',
+ },
+ ],
+ addedFunctions: [
+ {
+ targets: ['grafana-exploretraces-app/get-logs-drilldown-link/v1'],
+ title: 'Open Logs Drilldown',
+ description: 'Returns url to logs drilldown app',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.6.0',
+ grafanaVersion: '*',
+ plugins: [],
+ extensions: {
+ exposedComponents: [
+ 'grafana-adaptivelogs-app/temporary-exemptions/v1',
+ 'grafana-lokiexplore-app/embedded-logs-exploration/v1',
+ 'grafana-asserts-app/insights-timeline-widget/v1',
+ 'grafana/add-to-dashboard-form/v1',
+ ],
+ },
+ },
+ buildMode: 'production',
+ },
+ 'grafana-metricsdrilldown-app': {
+ id: 'grafana-metricsdrilldown-app',
+ path: 'public/plugins/grafana-metricsdrilldown-app/module.js',
+ version: '1.0.26',
+ preload: true,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ angular: { detected: false } as AngularMeta,
+ loadingStrategy: PluginLoadingStrategy.script,
+ extensions: {
+ addedLinks: [
+ {
+ targets: [
+ 'grafana/dashboard/panel/menu',
+ 'grafana/explore/toolbar/action',
+ 'grafana-assistant-app/navigateToDrilldown/v1',
+ 'grafana/alerting/alertingrule/queryeditor',
+ ],
+ title: 'Open in Grafana Metrics Drilldown',
+ description: 'Open current query in the Grafana Metrics Drilldown view',
+ },
+ {
+ targets: ['grafana-metricsdrilldown-app/grafana-assistant-app/navigateToDrilldown/v0-alpha'],
+ title: 'Navigate to metrics drilldown',
+ description: 'Build a url path to the metrics drilldown',
+ },
+ {
+ targets: ['grafana/datasources/config/actions', 'grafana/datasources/config/status'],
+ title: 'Open in Metrics Drilldown',
+ description: 'Browse metrics in Grafana Metrics Drilldown',
+ },
+ ],
+ addedComponents: [],
+ exposedComponents: [
+ {
+ id: 'grafana-metricsdrilldown-app/label-breakdown-component/v1',
+ title: 'Label Breakdown',
+ description: 'A metrics label breakdown view from the Metrics Drilldown app.',
+ },
+ {
+ id: 'grafana-metricsdrilldown-app/knowledge-graph-insight-metrics/v1',
+ title: 'Knowledge Graph Source Metrics',
+ description: 'Explore the underlying metrics related to a Knowledge Graph insight',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-exploremetrics-app/investigation/v1',
+ title: '',
+ description: '',
+ },
+ {
+ id: 'grafana-metricsdrilldown-app/open-in-logs-drilldown/v1',
+ title: '',
+ description: '',
+ },
+ ],
+ addedFunctions: [],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.6.0',
+ grafanaVersion: '*',
+ plugins: [],
+ extensions: {
+ exposedComponents: ['grafana/add-to-dashboard-form/v1'],
+ },
+ },
+ buildMode: 'production',
+ },
+ 'grafana-pyroscope-app': {
+ id: 'grafana-pyroscope-app',
+ path: 'public/plugins/grafana-pyroscope-app/module.js',
+ version: '1.14.2',
+ preload: true,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ angular: { detected: false } as AngularMeta,
+ loadingStrategy: PluginLoadingStrategy.script,
+ extensions: {
+ addedLinks: [
+ {
+ targets: [
+ 'grafana/explore/toolbar/action',
+ 'grafana/traceview/details',
+ 'grafana-assistant-app/navigateToDrilldown/v1',
+ ],
+ title: 'Open in Grafana Profiles Drilldown',
+ description: 'Try our new queryless experience for profiles',
+ },
+ ],
+ addedComponents: [],
+ exposedComponents: [
+ {
+ id: 'grafana-pyroscope-app/embedded-profiles-exploration/v1',
+ title: 'Embedded Profiles Exploration',
+ description:
+ 'A component that renders a profiles exploration view that can be embedded in other parts of Grafana.',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-pyroscope-app/investigation/v1',
+ title: '',
+ description: '',
+ },
+ {
+ id: 'grafana-pyroscope-app/settings/v1',
+ title: '',
+ description: '',
+ },
+ ],
+ addedFunctions: [],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.5.0',
+ grafanaVersion: '*',
+ plugins: [],
+ extensions: {
+ exposedComponents: [
+ 'grafana-o11yinsights-app/insights-launcher/v1',
+ 'grafana-adaptiveprofiles-app/resolution-boost/v1',
+ ],
+ },
+ },
+ buildMode: 'production',
+ },
+ [app.id]: app,
+});
diff --git a/packages/grafana-runtime/src/services/pluginMeta/test-fixtures/v0alpha1Response.ts b/packages/grafana-runtime/src/services/pluginMeta/test-fixtures/v0alpha1Response.ts
new file mode 100644
index 00000000000..7bd4c38d9fa
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/test-fixtures/v0alpha1Response.ts
@@ -0,0 +1,4378 @@
+import { cloneDeep } from 'lodash';
+
+import type { PluginMetasResponse } from '../types';
+import type { Meta } from '../types/meta_object_gen';
+
+export const v0alpha1Meta: Meta = cloneDeep({
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'myorg-someplugin-app',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'myorg-someplugin-app',
+ type: 'app',
+ name: 'Some-Plugin',
+ info: {
+ keywords: ['app'],
+ logos: {
+ small: 'public/plugins/myorg-someplugin-app/img/logo.svg',
+ large: 'public/plugins/myorg-someplugin-app/img/logo.svg',
+ },
+ updated: '2025-12-15',
+ version: '1.0.0',
+ author: {
+ name: 'Myorg',
+ },
+ },
+ dependencies: {
+ grafanaDependency: '>=10.4.0',
+ grafanaVersion: '*',
+ },
+ includes: [
+ {
+ type: 'page',
+ name: 'Page One',
+ role: 'Viewer',
+ action: 'plugins.app:access',
+ path: '/a/myorg-someplugin-app/one',
+ addToNav: true,
+ defaultNav: true,
+ },
+ {
+ type: 'page',
+ name: 'Page Two',
+ role: 'Viewer',
+ action: 'plugins.app:access',
+ path: '/a/myorg-someplugin-app/two',
+ addToNav: true,
+ },
+ {
+ type: 'page',
+ name: 'Page Three',
+ role: 'Viewer',
+ action: 'plugins.app:access',
+ path: '/a/myorg-someplugin-app/three',
+ addToNav: true,
+ },
+ {
+ type: 'page',
+ name: 'Page Four',
+ role: 'Viewer',
+ action: 'plugins.app:access',
+ path: '/a/myorg-someplugin-app/four',
+ addToNav: true,
+ },
+ {
+ type: 'page',
+ name: 'Configuration',
+ role: 'Admin',
+ path: '/plugins/myorg-someplugin-app',
+ addToNav: true,
+ icon: 'cog',
+ },
+ ],
+ },
+ class: 'external',
+ module: {
+ path: 'public/plugins/myorg-someplugin-app/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/myorg-someplugin-app',
+ signature: {
+ status: 'unsigned',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+});
+
+export const v0alpha1Response: PluginMetasResponse = cloneDeep({
+ items: [
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'alertlist',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'alertlist',
+ type: 'panel',
+ name: 'Alert list',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/alertlist/img/icn-singlestat-panel.svg',
+ large: 'public/plugins/alertlist/img/icn-singlestat-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Shows list of alerts and their current status',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/alert-list/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ skipDataQuery: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/alertlist',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/alertlist',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'alertmanager',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'alertmanager',
+ type: 'datasource',
+ name: 'Alertmanager',
+ info: {
+ keywords: ['alerts', 'alerting', 'prometheus', 'alertmanager', 'mimir', 'cortex'],
+ logos: {
+ small: 'public/plugins/alertmanager/img/logo.svg',
+ large: 'public/plugins/alertmanager/img/logo.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Prometheus alertmanager',
+ url: 'https://grafana.com',
+ },
+ description:
+ 'Add external Alertmanagers (supports Prometheus and Mimir implementations) so you can use the Grafana Alerting UI to manage silences, contact points, and notification policies.',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://prometheus.io/docs/alerting/latest/alertmanager/',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/alertmanager/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ routes: [
+ {
+ path: 'alertmanager/api/v2/silences',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'api/v2/silences',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'alertmanager/api/v2/silences',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.instances.external:write',
+ },
+ {
+ path: 'api/v2/silences',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.instances.external:write',
+ },
+ {
+ path: 'alertmanager/api/v2/silence/',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'api/v2/silence/',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'alertmanager/api/v2/silence/',
+ method: 'DELETE',
+ reqRole: 'Editor',
+ reqAction: 'alert.instances.external:write',
+ },
+ {
+ path: 'api/v2/silence/',
+ method: 'DELETE',
+ reqRole: 'Editor',
+ reqAction: 'alert.instances.external:write',
+ },
+ {
+ path: 'alertmanager/api/v2/alerts/groups',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'api/v2/alerts/groups',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'alertmanager/api/v2/alerts',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'api/v2/alerts',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'alertmanager/api/v2/alerts',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.instances.external:write',
+ },
+ {
+ path: 'api/v2/alerts',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.instances.external:write',
+ },
+ {
+ path: 'alertmanager/api/v2/status',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.notifications.external:read',
+ },
+ {
+ path: 'api/v2/status',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.notifications.external:read',
+ },
+ {
+ path: 'alertmanager/api/v2/receivers',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'api/v2/receivers',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.instances.external:read',
+ },
+ {
+ path: 'api/v1/alerts',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.notifications.external:read',
+ },
+ {
+ path: 'api/v1/alerts',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.notifications.external:write',
+ },
+ {
+ path: 'api/v1/alerts',
+ method: 'DELETE',
+ reqRole: 'Editor',
+ reqAction: 'alert.notifications.external:write',
+ },
+ {
+ method: 'POST',
+ reqRole: 'Admin',
+ },
+ {
+ method: 'PUT',
+ reqRole: 'Admin',
+ },
+ {
+ method: 'DELETE',
+ reqRole: 'Admin',
+ },
+ {
+ method: 'GET',
+ reqRole: 'Admin',
+ },
+ ],
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/alertmanager',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/alertmanager',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'annolist',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'annolist',
+ type: 'panel',
+ name: 'Annotations list',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/annolist/img/icn-annolist-panel.svg',
+ large: 'public/plugins/annolist/img/icn-annolist-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'List annotations',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/annotations/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ skipDataQuery: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/annolist',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/annolist',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'barchart',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'barchart',
+ type: 'panel',
+ name: 'Bar chart',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/barchart/img/barchart.svg',
+ large: 'public/plugins/barchart/img/barchart.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Categorical charts with group support',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/barchart',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/barchart',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'bargauge',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'bargauge',
+ type: 'panel',
+ name: 'Bar gauge',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/bargauge/img/icon_bar_gauge.svg',
+ large: 'public/plugins/bargauge/img/icon_bar_gauge.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Horizontal and vertical gauges',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-gauge/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/bargauge',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/bargauge',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'candlestick',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'candlestick',
+ type: 'panel',
+ name: 'Candlestick',
+ info: {
+ keywords: ['financial', 'price', 'currency', 'k-line'],
+ logos: {
+ small: 'public/plugins/candlestick/img/candlestick.svg',
+ large: 'public/plugins/candlestick/img/candlestick.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Graphical representation of price movements of a security, derivative, or currency.',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/candlestick/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/candlestick',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/candlestick',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'canvas',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'canvas',
+ type: 'panel',
+ name: 'Canvas',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/canvas/img/icn-canvas.svg',
+ large: 'public/plugins/canvas/img/icn-canvas.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Explicit element placement',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/canvas/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/canvas',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/canvas',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'cloudwatch',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'cloudwatch',
+ type: 'datasource',
+ name: 'CloudWatch',
+ info: {
+ keywords: ['aws', 'amazon'],
+ logos: {
+ small: 'public/plugins/cloudwatch/img/amazon-web-services.png',
+ large: 'public/plugins/cloudwatch/img/amazon-web-services.png',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Data source for Amazon AWS monitoring service',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'cloud',
+ includes: [
+ {
+ type: 'dashboard',
+ name: 'EC2',
+ role: 'Viewer',
+ path: 'dashboards/ec2.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'EBS',
+ role: 'Viewer',
+ path: 'dashboards/EBS.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Lambda',
+ role: 'Viewer',
+ path: 'dashboards/Lambda.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Logs',
+ role: 'Viewer',
+ path: 'dashboards/Logs.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'RDS',
+ role: 'Viewer',
+ path: 'dashboards/RDS.json',
+ },
+ ],
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/cloudwatch',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/cloudwatch',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'dashboard',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'dashboard',
+ type: 'datasource',
+ name: '-- Dashboard --',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/dashboard/img/icn-reusequeries.svg',
+ large: 'public/plugins/dashboard/img/icn-reusequeries.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Uses the result set from another panel in the same dashboard',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ builtIn: true,
+ metrics: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/dashboard',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/dashboard',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'dashlist',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'dashlist',
+ type: 'panel',
+ name: 'Dashboard list',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/dashlist/img/icn-dashlist-panel.svg',
+ large: 'public/plugins/dashlist/img/icn-dashlist-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'List of dynamic links to other dashboards',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/dashboard-list/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ skipDataQuery: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/dashlist',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/dashlist',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'datagrid',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'datagrid',
+ type: 'panel',
+ name: 'Datagrid',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/datagrid/img/icn-table-panel.svg',
+ large: 'public/plugins/datagrid/img/icn-table-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/datagrid/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ state: 'beta',
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/datagrid',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/datagrid',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'debug',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'debug',
+ type: 'panel',
+ name: 'Debug',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/debug/img/icn-debug.svg',
+ large: 'public/plugins/debug/img/icn-debug.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Debug Panel for Grafana',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ state: 'alpha',
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/debug',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/debug',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'elasticsearch',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'elasticsearch',
+ type: 'datasource',
+ name: 'Elasticsearch',
+ info: {
+ keywords: ['elasticsearch', 'datasource', 'database', 'logs', 'nosql', 'traces'],
+ logos: {
+ small: 'public/plugins/elasticsearch/img/elasticsearch.svg',
+ large: 'public/plugins/elasticsearch/img/elasticsearch.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Open source logging & analytics database',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://grafana.com/docs/features/datasources/elasticsearch/',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/elasticsearch/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'logging',
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/elasticsearch',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/elasticsearch',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'flamegraph',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'flamegraph',
+ type: 'panel',
+ name: 'Flame Graph',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/flamegraph/img/icn-flamegraph.svg',
+ large: 'public/plugins/flamegraph/img/icn-flamegraph.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/flame-graph/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/flamegraph',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/flamegraph',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'gauge',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'gauge',
+ type: 'panel',
+ name: 'Gauge',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/gauge/img/icon_gauge.svg',
+ large: 'public/plugins/gauge/img/icon_gauge.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Standard gauge visualization',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/gauge/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/gauge',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/gauge',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'geomap',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'geomap',
+ type: 'panel',
+ name: 'Geomap',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/geomap/img/icn-geomap.svg',
+ large: 'public/plugins/geomap/img/icn-geomap.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Geomap panel',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/geomap/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/geomap',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/geomap',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'gettingstarted',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'gettingstarted',
+ type: 'panel',
+ name: 'Getting Started',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/gettingstarted/img/icn-dashlist-panel.svg',
+ large: 'public/plugins/gettingstarted/img/icn-dashlist-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ hideFromList: true,
+ skipDataQuery: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/gettingstarted',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/gettingstarted',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana',
+ type: 'datasource',
+ name: '-- Grafana --',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/grafana/img/icn-grafanadb.svg',
+ large: 'public/plugins/grafana/img/icn-grafanadb.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description:
+ 'A built-in data source that generates random walk data and can poll the Testdata data source. This helps you test visualizations and run experiments.',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ annotations: true,
+ backend: true,
+ builtIn: true,
+ metrics: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/grafana',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-azure-monitor-datasource',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-azure-monitor-datasource',
+ type: 'datasource',
+ name: 'Azure Monitor',
+ info: {
+ keywords: ['azure', 'monitor', 'Application Insights', 'Log Analytics', 'App Insights'],
+ logos: {
+ small: 'public/plugins/grafana-azure-monitor-datasource/img/logo.jpg',
+ large: 'public/plugins/grafana-azure-monitor-datasource/img/logo.jpg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Data source for Microsoft Azure Monitor & Application Insights',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/azuremonitor/',
+ },
+ {
+ name: 'License',
+ url: 'https://github.com/grafana/grafana/blob/HEAD/LICENSE',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/azure-monitor/',
+ },
+ ],
+ screenshots: [
+ {
+ name: 'Azure Contoso Loans',
+ path: 'public/plugins/grafana-azure-monitor-datasource/img/contoso_loans_grafana_dashboard.png',
+ },
+ {
+ name: 'Azure Monitor Network',
+ path: 'public/plugins/grafana-azure-monitor-datasource/img/azure_monitor_network.png',
+ },
+ {
+ name: 'Azure Monitor CPU',
+ path: 'public/plugins/grafana-azure-monitor-datasource/img/azure_monitor_cpu.png',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'cloud',
+ executable: 'gpx_azuremonitor',
+ includes: [
+ {
+ type: 'dashboard',
+ name: 'Azure / Alert Consumption',
+ role: 'Viewer',
+ path: 'dashboards/v1Alerts.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Infrastructure / Apps Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/azureInfraApps.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Infrastructure / Compute Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/azureInfraCompute.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Infrastructure / Data Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/azureInfraData.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Infrastructure / Network Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/azureInfraNetwork.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Infrastructure / Storage and Key Vaults Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/azureInfraStorageVaults.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Azure PostgreSQL / Flexible Server Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/postgresFlexibleServer.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure Monitor / Container Insights / Syslog',
+ role: 'Viewer',
+ path: 'dashboards/containerInsightsSyslog.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications',
+ role: 'Viewer',
+ path: 'dashboards/appInsights.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications / Performance / Operations',
+ role: 'Viewer',
+ path: 'dashboards/appInsightsPerfOperations.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications / Performance / Dependencies',
+ role: 'Viewer',
+ path: 'dashboards/appInsightsPerfDependencies.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications / Failures / Operations',
+ role: 'Viewer',
+ path: 'dashboards/appInsightsFailureOperations.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications / Failures / Dependencies',
+ role: 'Viewer',
+ path: 'dashboards/appInsightsFailureDependencies.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications / Failures / Exceptions',
+ role: 'Viewer',
+ path: 'dashboards/appInsightsFailureExceptions.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Applications Test Availability Geo Map',
+ role: 'Viewer',
+ path: 'dashboards/appInsightsGeoMap.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / CosmosDB',
+ role: 'Viewer',
+ path: 'dashboards/cosmosdb.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Data Explorer Clusters',
+ role: 'Viewer',
+ path: 'dashboards/adx.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Key Vaults',
+ role: 'Viewer',
+ path: 'dashboards/keyvault.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Networks',
+ role: 'Viewer',
+ path: 'dashboards/networkInsightsDashboard.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / SQL Database',
+ role: 'Viewer',
+ path: 'dashboards/sqldb.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Storage Accounts',
+ role: 'Viewer',
+ path: 'dashboards/storage.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Virtual Machines by Resource Group',
+ role: 'Viewer',
+ path: 'dashboards/vMInsightsRG.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Insights / Virtual Machines by Workspace',
+ role: 'Viewer',
+ path: 'dashboards/vMInsightsWorkspace.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Azure / Resources Overview',
+ role: 'Viewer',
+ path: 'dashboards/arg.json',
+ },
+ ],
+ logs: true,
+ metrics: true,
+ tracing: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/grafana-azure-monitor-datasource/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-azure-monitor-datasource',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ translations: {
+ 'cs-CZ':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/cs-CZ/grafana-azure-monitor-datasource.json',
+ 'de-DE':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/de-DE/grafana-azure-monitor-datasource.json',
+ 'en-US':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/en-US/grafana-azure-monitor-datasource.json',
+ 'es-ES':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/es-ES/grafana-azure-monitor-datasource.json',
+ 'fr-FR':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/fr-FR/grafana-azure-monitor-datasource.json',
+ 'hu-HU':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/hu-HU/grafana-azure-monitor-datasource.json',
+ 'id-ID':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/id-ID/grafana-azure-monitor-datasource.json',
+ 'it-IT':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/it-IT/grafana-azure-monitor-datasource.json',
+ 'ja-JP':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/ja-JP/grafana-azure-monitor-datasource.json',
+ 'ko-KR':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/ko-KR/grafana-azure-monitor-datasource.json',
+ 'nl-NL':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/nl-NL/grafana-azure-monitor-datasource.json',
+ 'pl-PL':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/pl-PL/grafana-azure-monitor-datasource.json',
+ 'pt-BR':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/pt-BR/grafana-azure-monitor-datasource.json',
+ 'pt-PT':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/pt-PT/grafana-azure-monitor-datasource.json',
+ 'ru-RU':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/ru-RU/grafana-azure-monitor-datasource.json',
+ 'sv-SE':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/sv-SE/grafana-azure-monitor-datasource.json',
+ 'tr-TR':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/tr-TR/grafana-azure-monitor-datasource.json',
+ 'zh-Hans':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/zh-Hans/grafana-azure-monitor-datasource.json',
+ 'zh-Hant':
+ 'public/plugins/grafana-azure-monitor-datasource/locales/zh-Hant/grafana-azure-monitor-datasource.json',
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-exploretraces-app',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-exploretraces-app',
+ type: 'app',
+ name: 'Grafana Traces Drilldown',
+ info: {
+ keywords: ['app', 'tempo', 'traces', 'explore'],
+ logos: {
+ small: 'public/plugins/grafana-exploretraces-app/img/logo.svg',
+ large: 'public/plugins/grafana-exploretraces-app/img/logo.svg',
+ },
+ updated: '2025-12-04',
+ version: '1.2.2',
+ author: {
+ name: 'Grafana',
+ },
+ description:
+ 'Use Rate, Errors, and Duration (RED) metrics derived from traces to investigate errors within complex distributed systems.',
+ links: [
+ {
+ name: 'Github',
+ url: 'https://github.com/grafana/explore-traces',
+ },
+ {
+ name: 'Report bug',
+ url: 'https://github.com/grafana/explore-traces/issues/new',
+ },
+ ],
+ screenshots: [
+ {
+ name: 'histogram-breakdown',
+ path: 'public/plugins/grafana-exploretraces-app/img/histogram-breakdown.png',
+ },
+ {
+ name: 'errors-metric-flow',
+ path: 'public/plugins/grafana-exploretraces-app/img/errors-metric-flow.png',
+ },
+ {
+ name: 'errors-root-cause',
+ path: 'public/plugins/grafana-exploretraces-app/img/errors-root-cause.png',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.5.0',
+ grafanaVersion: '*',
+ extensions: {
+ exposedComponents: [
+ 'grafana-asserts-app/entity-assertions-widget/v1',
+ 'grafana-asserts-app/insights-timeline-widget/v1',
+ ],
+ },
+ },
+ autoEnabled: true,
+ includes: [
+ {
+ type: 'page',
+ name: 'Explore',
+ role: 'Viewer',
+ action: 'datasources:explore',
+ path: '/a/grafana-exploretraces-app/',
+ addToNav: true,
+ defaultNav: true,
+ },
+ ],
+ preload: true,
+ extensions: {
+ addedComponents: [
+ {
+ targets: ['grafana-asserts-app/entity-assertions-widget/v1'],
+ title: 'Asserts widget',
+ description: 'A block with assertions for a given service',
+ },
+ {
+ targets: ['grafana-asserts-app/insights-timeline-widget/v1'],
+ title: 'Insights Timeline Widget',
+ description: 'Widget for displaying insights timeline in other apps',
+ },
+ ],
+ addedLinks: [
+ {
+ targets: ['grafana/dashboard/panel/menu'],
+ title: 'Open in Traces Drilldown',
+ description: 'Open current query in the Traces Drilldown app',
+ },
+ {
+ targets: ['grafana/explore/toolbar/action'],
+ title: 'Open in Grafana Traces Drilldown',
+ description: 'Try our new queryless experience for traces',
+ },
+ ],
+ exposedComponents: [
+ {
+ id: 'grafana-exploretraces-app/open-in-explore-traces-button/v1',
+ title: 'Open in Traces Drilldown button',
+ description: 'A button that opens a traces view in the Traces Drilldown app.',
+ },
+ {
+ id: 'grafana-exploretraces-app/embedded-trace-exploration/v1',
+ title: 'Embedded Trace Exploration',
+ description:
+ 'A component that renders a trace exploration view that can be embedded in other parts of Grafana.',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-exploretraces-app/investigation/v1',
+ },
+ {
+ id: 'grafana-exploretraces-app/get-logs-drilldown-link/v1',
+ },
+ ],
+ },
+ },
+ class: 'external',
+ module: {
+ path: 'public/plugins/grafana-exploretraces-app/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-exploretraces-app',
+ signature: {
+ status: 'valid',
+ type: 'grafana',
+ org: 'Grafana Labs',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-lokiexplore-app',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-lokiexplore-app',
+ type: 'app',
+ name: 'Grafana Logs Drilldown',
+ info: {
+ keywords: ['app', 'loki', 'explore', 'logs', 'drilldown', 'drill', 'down', 'drill-down'],
+ logos: {
+ small: 'public/plugins/grafana-lokiexplore-app/img/logo.svg',
+ large: 'public/plugins/grafana-lokiexplore-app/img/logo.svg',
+ },
+ updated: '2025-12-09',
+ version: '1.0.32',
+ author: {
+ name: 'Grafana',
+ },
+ description:
+ 'Visualize log volumes to easily detect anomalies or significant changes over time, without needing to compose LogQL queries.',
+ links: [
+ {
+ name: 'Github',
+ url: 'https://github.com/grafana/explore-logs',
+ },
+ {
+ name: 'Report bug',
+ url: 'https://github.com/grafana/explore-logs/issues/new',
+ },
+ ],
+ screenshots: [
+ {
+ name: 'patterns',
+ path: 'public/plugins/grafana-lokiexplore-app/img/patterns.png',
+ },
+ {
+ name: 'fields',
+ path: 'public/plugins/grafana-lokiexplore-app/img/fields.png',
+ },
+ {
+ name: 'table',
+ path: 'public/plugins/grafana-lokiexplore-app/img/table.png',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.6.0',
+ grafanaVersion: '*',
+ extensions: {
+ exposedComponents: [
+ 'grafana-adaptivelogs-app/temporary-exemptions/v1',
+ 'grafana-lokiexplore-app/embedded-logs-exploration/v1',
+ 'grafana-asserts-app/insights-timeline-widget/v1',
+ 'grafana/add-to-dashboard-form/v1',
+ ],
+ },
+ },
+ autoEnabled: true,
+ includes: [
+ {
+ type: 'page',
+ name: 'Grafana Logs Drilldown',
+ role: 'Viewer',
+ action: 'datasources:explore',
+ path: '/a/grafana-lokiexplore-app/explore',
+ addToNav: true,
+ defaultNav: true,
+ },
+ ],
+ preload: true,
+ extensions: {
+ addedComponents: [
+ {
+ targets: ['grafana-asserts-app/insights-timeline-widget/v1'],
+ title: 'Insights Timeline Widget',
+ description: 'Widget for displaying insights timeline in other apps',
+ },
+ ],
+ addedLinks: [
+ {
+ targets: [
+ 'grafana/dashboard/panel/menu',
+ 'grafana/explore/toolbar/action',
+ 'grafana-metricsdrilldown-app/open-in-logs-drilldown/v1',
+ 'grafana-assistant-app/navigateToDrilldown/v1',
+ ],
+ title: 'Open in Grafana Logs Drilldown',
+ description: 'Open current query in the Grafana Logs Drilldown view',
+ },
+ ],
+ addedFunctions: [
+ {
+ targets: ['grafana-exploretraces-app/get-logs-drilldown-link/v1'],
+ title: 'Open Logs Drilldown',
+ description: 'Returns url to logs drilldown app',
+ },
+ ],
+ exposedComponents: [
+ {
+ id: 'grafana-lokiexplore-app/open-in-explore-logs-button/v1',
+ title: 'Open in Logs Drilldown button',
+ description: 'A button that opens a logs view in the Logs Drilldown app.',
+ },
+ {
+ id: 'grafana-lokiexplore-app/embedded-logs-exploration/v1',
+ title: 'Embedded Logs Exploration',
+ description:
+ 'A component that renders a logs exploration view that can be embedded in other parts of Grafana.',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-lokiexplore-app/investigation/v1',
+ },
+ ],
+ },
+ },
+ class: 'external',
+ module: {
+ path: 'public/plugins/grafana-lokiexplore-app/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-lokiexplore-app',
+ signature: {
+ status: 'valid',
+ type: 'grafana',
+ org: 'Grafana Labs',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-metricsdrilldown-app',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-metricsdrilldown-app',
+ type: 'app',
+ name: 'Grafana Metrics Drilldown',
+ info: {
+ keywords: ['drilldown', 'metrics', 'app', 'prometheus', 'mimir'],
+ logos: {
+ small: 'public/plugins/grafana-metricsdrilldown-app/img/logo.svg',
+ large: 'public/plugins/grafana-metricsdrilldown-app/img/logo.svg',
+ },
+ updated: '2025-12-17',
+ version: '1.0.26',
+ author: {
+ name: 'Grafana',
+ },
+ description:
+ 'Quickly find related metrics with a few clicks, without needing to write PromQL queries to retrieve metrics.',
+ links: [
+ {
+ name: 'GitHub',
+ url: 'https://github.com/grafana/metrics-drilldown',
+ },
+ {
+ name: 'Report a bug',
+ url: 'https://github.com/grafana/metrics-drilldown/issues/new',
+ },
+ ],
+ screenshots: [
+ {
+ name: 'metricselect',
+ path: 'public/plugins/grafana-metricsdrilldown-app/img/metrics-drilldown.png',
+ },
+ {
+ name: 'breakdown',
+ path: 'public/plugins/grafana-metricsdrilldown-app/img/breakdown.png',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.6.0',
+ grafanaVersion: '*',
+ extensions: {
+ exposedComponents: ['grafana/add-to-dashboard-form/v1'],
+ },
+ },
+ autoEnabled: true,
+ includes: [
+ {
+ type: 'page',
+ name: 'Grafana Metrics Drilldown',
+ role: 'Viewer',
+ action: 'datasources:explore',
+ path: '/a/grafana-metricsdrilldown-app/drilldown',
+ addToNav: true,
+ defaultNav: true,
+ },
+ ],
+ preload: true,
+ extensions: {
+ addedLinks: [
+ {
+ targets: [
+ 'grafana/dashboard/panel/menu',
+ 'grafana/explore/toolbar/action',
+ 'grafana-assistant-app/navigateToDrilldown/v1',
+ 'grafana/alerting/alertingrule/queryeditor',
+ ],
+ title: 'Open in Grafana Metrics Drilldown',
+ description: 'Open current query in the Grafana Metrics Drilldown view',
+ },
+ {
+ targets: ['grafana-metricsdrilldown-app/grafana-assistant-app/navigateToDrilldown/v0-alpha'],
+ title: 'Navigate to metrics drilldown',
+ description: 'Build a url path to the metrics drilldown',
+ },
+ {
+ targets: ['grafana/datasources/config/actions', 'grafana/datasources/config/status'],
+ title: 'Open in Metrics Drilldown',
+ description: 'Browse metrics in Grafana Metrics Drilldown',
+ },
+ ],
+ exposedComponents: [
+ {
+ id: 'grafana-metricsdrilldown-app/label-breakdown-component/v1',
+ title: 'Label Breakdown',
+ description: 'A metrics label breakdown view from the Metrics Drilldown app.',
+ },
+ {
+ id: 'grafana-metricsdrilldown-app/knowledge-graph-insight-metrics/v1',
+ title: 'Knowledge Graph Source Metrics',
+ description: 'Explore the underlying metrics related to a Knowledge Graph insight',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-exploremetrics-app/investigation/v1',
+ },
+ {
+ id: 'grafana-metricsdrilldown-app/open-in-logs-drilldown/v1',
+ },
+ ],
+ },
+ },
+ class: 'external',
+ module: {
+ path: 'public/plugins/grafana-metricsdrilldown-app/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-metricsdrilldown-app',
+ signature: {
+ status: 'valid',
+ type: 'grafana',
+ org: 'Grafana Labs',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-postgresql-datasource',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-postgresql-datasource',
+ type: 'datasource',
+ name: 'PostgreSQL',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/grafana-postgresql-datasource/img/postgresql_logo.svg',
+ large: 'public/plugins/grafana-postgresql-datasource/img/postgresql_logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Data source for PostgreSQL and compatible databases',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/postgres/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.6.0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'sql',
+ executable: 'gpx_grafana-postgresql-datasource',
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/grafana-postgresql-datasource/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-postgresql-datasource',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-pyroscope-app',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-pyroscope-app',
+ type: 'app',
+ name: 'Grafana Profiles Drilldown',
+ info: {
+ keywords: ['app', 'pyroscope', 'profiling', 'explore', 'profiles', 'performance', 'drilldown'],
+ logos: {
+ small: 'public/plugins/grafana-pyroscope-app/img/logo.svg',
+ large: 'public/plugins/grafana-pyroscope-app/img/logo.svg',
+ },
+ updated: '2025-12-18',
+ version: '1.14.2',
+ author: {
+ name: 'Grafana',
+ },
+ description:
+ 'View and analyze high-level service performance, identify problem processes for optimization, and diagnose issues to determine root causes.',
+ links: [
+ {
+ name: 'GitHub',
+ url: 'https://github.com/grafana/profiles-drilldown',
+ },
+ {
+ name: 'Report bug',
+ url: 'https://github.com/grafana/profiles-drilldown/issues/new',
+ },
+ ],
+ screenshots: [
+ {
+ name: 'Hero Image',
+ path: 'public/plugins/grafana-pyroscope-app/img/hero-image.png',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=11.5.0',
+ grafanaVersion: '*',
+ extensions: {
+ exposedComponents: [
+ 'grafana-o11yinsights-app/insights-launcher/v1',
+ 'grafana-adaptiveprofiles-app/resolution-boost/v1',
+ ],
+ },
+ },
+ autoEnabled: true,
+ includes: [
+ {
+ type: 'page',
+ name: 'Profiles',
+ role: 'Viewer',
+ action: 'datasources:explore',
+ path: '/a/grafana-pyroscope-app/explore',
+ addToNav: true,
+ defaultNav: true,
+ },
+ ],
+ preload: true,
+ extensions: {
+ addedLinks: [
+ {
+ targets: [
+ 'grafana/explore/toolbar/action',
+ 'grafana/traceview/details',
+ 'grafana-assistant-app/navigateToDrilldown/v1',
+ ],
+ title: 'Open in Grafana Profiles Drilldown',
+ description: 'Try our new queryless experience for profiles',
+ },
+ ],
+ exposedComponents: [
+ {
+ id: 'grafana-pyroscope-app/embedded-profiles-exploration/v1',
+ title: 'Embedded Profiles Exploration',
+ description:
+ 'A component that renders a profiles exploration view that can be embedded in other parts of Grafana.',
+ },
+ ],
+ extensionPoints: [
+ {
+ id: 'grafana-pyroscope-app/investigation/v1',
+ },
+ {
+ id: 'grafana-pyroscope-app/settings/v1',
+ },
+ ],
+ },
+ },
+ class: 'external',
+ module: {
+ path: 'public/plugins/grafana-pyroscope-app/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-pyroscope-app',
+ signature: {
+ status: 'valid',
+ type: 'grafana',
+ org: 'Grafana Labs',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-pyroscope-datasource',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-pyroscope-datasource',
+ type: 'datasource',
+ name: 'Grafana Pyroscope',
+ info: {
+ keywords: [
+ 'grafana',
+ 'datasource',
+ 'phlare',
+ 'flamegraph',
+ 'profiling',
+ 'continuous profiling',
+ 'pyroscope',
+ ],
+ logos: {
+ small: 'public/plugins/grafana-pyroscope-datasource/img/grafana_pyroscope_icon.svg',
+ large: 'public/plugins/grafana-pyroscope-datasource/img/grafana_pyroscope_icon.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://www.grafana.com',
+ },
+ description:
+ 'Data source for Grafana Pyroscope, horizontally-scalable, highly-available, multi-tenant continuous profiling aggregation system.',
+ links: [
+ {
+ name: 'GitHub Project',
+ url: 'https://github.com/grafana/pyroscope',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/pyroscope/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/pyroscope/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ backend: true,
+ category: 'profiling',
+ executable: 'gpx_grafana-pyroscope-datasource',
+ metrics: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/grafana-pyroscope-datasource/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-pyroscope-datasource',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'grafana-testdata-datasource',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'grafana-testdata-datasource',
+ type: 'datasource',
+ name: 'TestData',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/grafana-testdata-datasource/img/testdata.svg',
+ large: 'public/plugins/grafana-testdata-datasource/img/testdata.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Generates test data in different forms',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/testdata/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ executable: 'gpx_testdata',
+ includes: [
+ {
+ type: 'dashboard',
+ name: 'Streaming Example',
+ role: 'Viewer',
+ path: 'dashboards/streaming.json',
+ },
+ ],
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ maxDataPoints: true,
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/grafana-testdata-datasource/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/grafana-testdata-datasource',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'graphite',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'graphite',
+ type: 'datasource',
+ name: 'Graphite',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/graphite/img/graphite_logo.png',
+ large: 'public/plugins/graphite/img/graphite_logo.png',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Open source time series database',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://graphiteapp.org/',
+ },
+ {
+ name: 'Graphite 1.1 Release',
+ url: 'https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/graphite/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'tsdb',
+ executable: 'gpx_graphite',
+ includes: [
+ {
+ type: 'dashboard',
+ name: 'Graphite Carbon Metrics',
+ role: 'Viewer',
+ path: 'dashboards/carbon_metrics.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Metrictank (Graphite alternative)',
+ role: 'Viewer',
+ path: 'dashboards/metrictank.json',
+ },
+ ],
+ metrics: true,
+ queryOptions: {
+ maxDataPoints: true,
+ cacheTimeout: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/graphite/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/graphite',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'heatmap',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'heatmap',
+ type: 'panel',
+ name: 'Heatmap',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/heatmap/img/icn-heatmap-panel.svg',
+ large: 'public/plugins/heatmap/img/icn-heatmap-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Like a histogram over time',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/heatmap/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/heatmap',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/heatmap',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'histogram',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'histogram',
+ type: 'panel',
+ name: 'Histogram',
+ info: {
+ keywords: ['distribution', 'bar chart', 'frequency', 'proportional'],
+ logos: {
+ small: 'public/plugins/histogram/img/histogram.svg',
+ large: 'public/plugins/histogram/img/histogram.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Distribution of values presented as a bar chart.',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/histogram/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/histogram',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/histogram',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'influxdb',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'influxdb',
+ type: 'datasource',
+ name: 'InfluxDB',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/influxdb/img/influxdb_logo.svg',
+ large: 'public/plugins/influxdb/img/influxdb_logo.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Open source time series database',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/influxdb/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'tsdb',
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/influxdb',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/influxdb',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'jaeger',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'jaeger',
+ type: 'datasource',
+ name: 'Jaeger',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/jaeger/img/jaeger_logo.svg',
+ large: 'public/plugins/jaeger/img/jaeger_logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Open source, end-to-end distributed tracing',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://www.jaegertracing.io',
+ },
+ {
+ name: 'Jaeger GitHub Project',
+ url: 'https://github.com/jaegertracing/jaeger',
+ },
+ {
+ name: 'Repository',
+ url: 'https://github.com/grafana/grafana',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/jaeger/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ backend: true,
+ category: 'tracing',
+ executable: 'gpx_jaeger',
+ metrics: true,
+ tracing: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/jaeger/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/jaeger',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'live',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'live',
+ type: 'panel',
+ name: 'Live',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/live/img/live.svg',
+ large: 'public/plugins/live/img/live.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ skipDataQuery: true,
+ state: 'alpha',
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/live',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/live',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'logs',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'logs',
+ type: 'panel',
+ name: 'Logs',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/logs/img/icn-logs-panel.svg',
+ large: 'public/plugins/logs/img/icn-logs-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/logs/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/logs',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/logs',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'loki',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'loki',
+ type: 'datasource',
+ name: 'Loki',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/loki/img/loki_icon.svg',
+ large: 'public/plugins/loki/img/loki_icon.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Like Prometheus but for logs. OSS logging solution from Grafana Labs',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://grafana.com/loki',
+ },
+ {
+ name: 'GitHub Project',
+ url: 'https://github.com/grafana/loki',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/loki/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.4.0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'logging',
+ executable: 'gpx_loki',
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ maxDataPoints: true,
+ },
+ streaming: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/loki/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/loki',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'mixed',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'mixed',
+ type: 'datasource',
+ name: '-- Mixed --',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/mixed/img/icn-mixeddatasources.svg',
+ large: 'public/plugins/mixed/img/icn-mixeddatasources.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Lets you query multiple data sources in the same panel.',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/#special-data-sources',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ builtIn: true,
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/mixed',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/mixed',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'mssql',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'mssql',
+ type: 'datasource',
+ name: 'Microsoft SQL Server',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/mssql/img/sql_server_logo.svg',
+ large: 'public/plugins/mssql/img/sql_server_logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Data source for Microsoft SQL Server compatible databases',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/mssql/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.4.0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'sql',
+ executable: 'gpx_mssql',
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/mssql/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/mssql',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ translations: {
+ 'cs-CZ': 'public/plugins/mssql/locales/cs-CZ/mssql.json',
+ 'de-DE': 'public/plugins/mssql/locales/de-DE/mssql.json',
+ 'en-US': 'public/plugins/mssql/locales/en-US/mssql.json',
+ 'es-ES': 'public/plugins/mssql/locales/es-ES/mssql.json',
+ 'fr-FR': 'public/plugins/mssql/locales/fr-FR/mssql.json',
+ 'hu-HU': 'public/plugins/mssql/locales/hu-HU/mssql.json',
+ 'id-ID': 'public/plugins/mssql/locales/id-ID/mssql.json',
+ 'it-IT': 'public/plugins/mssql/locales/it-IT/mssql.json',
+ 'ja-JP': 'public/plugins/mssql/locales/ja-JP/mssql.json',
+ 'ko-KR': 'public/plugins/mssql/locales/ko-KR/mssql.json',
+ 'nl-NL': 'public/plugins/mssql/locales/nl-NL/mssql.json',
+ 'pl-PL': 'public/plugins/mssql/locales/pl-PL/mssql.json',
+ 'pt-BR': 'public/plugins/mssql/locales/pt-BR/mssql.json',
+ 'pt-PT': 'public/plugins/mssql/locales/pt-PT/mssql.json',
+ 'ru-RU': 'public/plugins/mssql/locales/ru-RU/mssql.json',
+ 'sv-SE': 'public/plugins/mssql/locales/sv-SE/mssql.json',
+ 'tr-TR': 'public/plugins/mssql/locales/tr-TR/mssql.json',
+ 'zh-Hans': 'public/plugins/mssql/locales/zh-Hans/mssql.json',
+ 'zh-Hant': 'public/plugins/mssql/locales/zh-Hant/mssql.json',
+ },
+ },
+ status: {},
+ },
+ v0alpha1Meta,
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'mysql',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'mysql',
+ type: 'datasource',
+ name: 'MySQL',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/mysql/img/mysql_logo.svg',
+ large: 'public/plugins/mysql/img/mysql_logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Data source for MySQL databases',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/mysql/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.4.0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'sql',
+ executable: 'gpx_mysql',
+ metrics: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/mysql/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/mysql',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'news',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'news',
+ type: 'panel',
+ name: 'News',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/news/img/news.svg',
+ large: 'public/plugins/news/img/news.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'RSS feed reader',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/news/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ skipDataQuery: true,
+ state: 'beta',
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/news',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/news',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'nodeGraph',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'nodeGraph',
+ type: 'panel',
+ name: 'Node Graph',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/nodeGraph/img/icn-node-graph.svg',
+ large: 'public/plugins/nodeGraph/img/icn-node-graph.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/node-graph/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/nodeGraph',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/nodeGraph',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'opentsdb',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'opentsdb',
+ type: 'datasource',
+ name: 'OpenTSDB',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/opentsdb/img/opentsdb_logo.png',
+ large: 'public/plugins/opentsdb/img/opentsdb_logo.png',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Open source time series database',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/opentsdb/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'tsdb',
+ executable: 'gpx_opentsdb',
+ metrics: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/opentsdb/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/opentsdb',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'parca',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'parca',
+ type: 'datasource',
+ name: 'Parca',
+ info: {
+ keywords: ['grafana', 'datasource', 'parca', 'profiling'],
+ logos: {
+ small: 'public/plugins/parca/img/logo-small.svg',
+ large: 'public/plugins/parca/img/logo-small.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://www.grafana.com',
+ },
+ description:
+ 'Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.',
+ links: [
+ {
+ name: 'GitHub Project',
+ url: 'https://github.com/parca-dev/parca',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/parca/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ backend: true,
+ category: 'profiling',
+ executable: 'gpx_parca',
+ metrics: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/parca/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/parca',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'piechart',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'piechart',
+ type: 'panel',
+ name: 'Pie chart',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/piechart/img/icon_piechart.svg',
+ large: 'public/plugins/piechart/img/icon_piechart.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'The new core pie chart visualization',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/pie-chart/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/piechart',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/piechart',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'prometheus',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'prometheus',
+ type: 'datasource',
+ name: 'Prometheus',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/prometheus/img/prometheus_logo.svg',
+ large: 'public/plugins/prometheus/img/prometheus_logo.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Open source time series database & alerting',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://prometheus.io/',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/prometheus/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'tsdb',
+ includes: [
+ {
+ type: 'dashboard',
+ name: 'Prometheus Stats',
+ role: 'Viewer',
+ path: 'dashboards/prometheus_stats.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Prometheus 2.0 Stats',
+ role: 'Viewer',
+ path: 'dashboards/prometheus_2_stats.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Grafana Stats',
+ role: 'Viewer',
+ path: 'dashboards/grafana_stats.json',
+ },
+ ],
+ metrics: true,
+ multiValueFilterOperators: true,
+ queryOptions: {
+ minInterval: true,
+ },
+ routes: [
+ {
+ path: 'api/v1/query',
+ method: 'POST',
+ reqRole: 'Viewer',
+ reqAction: 'datasources:query',
+ },
+ {
+ path: 'api/v1/query_range',
+ method: 'POST',
+ reqRole: 'Viewer',
+ reqAction: 'datasources:query',
+ },
+ {
+ path: 'api/v1/series',
+ method: 'POST',
+ reqRole: 'Viewer',
+ reqAction: 'datasources:query',
+ },
+ {
+ path: 'api/v1/labels',
+ method: 'POST',
+ reqRole: 'Viewer',
+ reqAction: 'datasources:query',
+ },
+ {
+ path: 'api/v1/query_exemplars',
+ method: 'POST',
+ reqRole: 'Viewer',
+ reqAction: 'datasources:query',
+ },
+ {
+ path: '/rules',
+ method: 'GET',
+ reqRole: 'Viewer',
+ reqAction: 'alert.rules.external:read',
+ },
+ {
+ path: '/rules',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.rules.external:write',
+ },
+ {
+ path: '/rules',
+ method: 'DELETE',
+ reqRole: 'Editor',
+ reqAction: 'alert.rules.external:write',
+ },
+ {
+ path: '/config/v1/rules',
+ method: 'DELETE',
+ reqRole: 'Editor',
+ reqAction: 'alert.rules.external:write',
+ },
+ {
+ path: '/config/v1/rules',
+ method: 'POST',
+ reqRole: 'Editor',
+ reqAction: 'alert.rules.external:write',
+ },
+ ],
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/prometheus',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/prometheus',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'radialbar',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'radialbar',
+ type: 'panel',
+ name: 'New Gauge',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/radialbar/img/icon_gauge.svg',
+ large: 'public/plugins/radialbar/img/icon_gauge.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Standard gauge visualization',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/gauge/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ state: 'alpha',
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/radialbar',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/radialbar',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'stackdriver',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'stackdriver',
+ type: 'datasource',
+ name: 'Google Cloud Monitoring',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/stackdriver/img/cloud_monitoring_logo.svg',
+ large: 'public/plugins/stackdriver/img/cloud_monitoring_logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: "Data source for Google's monitoring service (formerly named Stackdriver)",
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ alerting: true,
+ annotations: true,
+ backend: true,
+ category: 'cloud',
+ executable: 'gpx_cloudmonitoring',
+ includes: [
+ {
+ type: 'dashboard',
+ name: 'Data Processing Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/dataprocessing-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Cloud Functions Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/cloudfunctions-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'GCE VM Instance Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/gce-vm-instance-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'GKE Prometheus Pod/Node Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/gke-prometheus-pod-node-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Firewall Insights Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/firewall-insight-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'GCE Network Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/gce-network-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'HTTP/S LB Backend Services',
+ role: 'Viewer',
+ path: 'dashboards/https-lb-backend-services-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'HTTP/S Load Balancer Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/https-loadbalancer-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Network TCP Load Balancer Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/network-tcp-loadbalancer-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'MicroService Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/micro-service-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Cloud Storage Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/cloud-storage-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Cloud SQL Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/cloudsql-monitoring.json',
+ },
+ {
+ type: 'dashboard',
+ name: 'Cloud SQL(MySQL) Monitoring',
+ role: 'Viewer',
+ path: 'dashboards/cloudsql-mysql-monitoring.json',
+ },
+ ],
+ logs: true,
+ metrics: true,
+ queryOptions: {
+ maxDataPoints: true,
+ cacheTimeout: true,
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/stackdriver/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/stackdriver',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'stat',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'stat',
+ type: 'panel',
+ name: 'Stat',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/stat/img/icn-singlestat-panel.svg',
+ large: 'public/plugins/stat/img/icn-singlestat-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Big stat values & sparklines',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/stat/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/stat',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/stat',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'state-timeline',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'state-timeline',
+ type: 'panel',
+ name: 'State timeline',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/state-timeline/img/timeline.svg',
+ large: 'public/plugins/state-timeline/img/timeline.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'State changes and durations',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/state-timeline/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/state-timeline',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/state-timeline',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'status-history',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'status-history',
+ type: 'panel',
+ name: 'Status history',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/status-history/img/status.svg',
+ large: 'public/plugins/status-history/img/status.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Periodic status history',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/status-history/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/status-history',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/status-history',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'table',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'table',
+ type: 'panel',
+ name: 'Table',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/table/img/icn-table-panel.svg',
+ large: 'public/plugins/table/img/icn-table-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Supports many column styles',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/table/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/table',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/table',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'tempo',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'tempo',
+ type: 'datasource',
+ name: 'Tempo',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/tempo/img/tempo_logo.svg',
+ large: 'public/plugins/tempo/img/tempo_logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'High volume, minimal dependency trace storage. OSS tracing solution from Grafana Labs.',
+ links: [
+ {
+ name: 'GitHub Project',
+ url: 'https://github.com/grafana/tempo',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/tempo/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ backend: true,
+ category: 'tracing',
+ executable: 'gpx_tempo',
+ metrics: true,
+ tracing: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/tempo/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/tempo',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'text',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'text',
+ type: 'panel',
+ name: 'Text',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/text/img/icn-text-panel.svg',
+ large: 'public/plugins/text/img/icn-text-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Supports markdown and html content',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/text/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ skipDataQuery: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/text',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/text',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'timeseries',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'timeseries',
+ type: 'panel',
+ name: 'Time series',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/timeseries/img/icn-timeseries-panel.svg',
+ large: 'public/plugins/timeseries/img/icn-timeseries-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Time based line, area and bar charts',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/time-series/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/timeseries',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/timeseries',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'traces',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'traces',
+ type: 'panel',
+ name: 'Traces',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/traces/img/traces-panel.svg',
+ large: 'public/plugins/traces/img/traces-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/traces/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/traces',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/traces',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'trend',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'trend',
+ type: 'panel',
+ name: 'Trend',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/trend/img/trend.svg',
+ large: 'public/plugins/trend/img/trend.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Like timeseries, but when x != time',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/trend/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ state: 'beta',
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/trend',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/trend',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'welcome',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'welcome',
+ type: 'panel',
+ name: 'Welcome',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/welcome/img/icn-dashlist-panel.svg',
+ large: 'public/plugins/welcome/img/icn-dashlist-panel.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ hideFromList: true,
+ skipDataQuery: true,
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/welcome',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/welcome',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'xychart',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'xychart',
+ type: 'panel',
+ name: 'XY Chart',
+ info: {
+ keywords: ['scatter', 'plot'],
+ logos: {
+ small: 'public/plugins/xychart/img/icn-xychart.svg',
+ large: 'public/plugins/xychart/img/icn-xychart.svg',
+ },
+ updated: '',
+ version: '',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Supports arbitrary X vs Y in a graph to visualize the relationship between two variables.',
+ links: [
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/xy-chart/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '',
+ grafanaVersion: '*',
+ },
+ },
+ class: 'core',
+ module: {
+ path: 'core:plugin/xychart',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/xychart',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ {
+ kind: 'Meta',
+ apiVersion: 'plugins.grafana.app/v0alpha1',
+ metadata: {
+ name: 'zipkin',
+ namespace: 'default',
+ },
+ spec: {
+ pluginJson: {
+ id: 'zipkin',
+ type: 'datasource',
+ name: 'Zipkin',
+ info: {
+ keywords: [],
+ logos: {
+ small: 'public/plugins/zipkin/img/zipkin-logo.svg',
+ large: 'public/plugins/zipkin/img/zipkin-logo.svg',
+ },
+ updated: '',
+ version: '12.4.0-pre',
+ author: {
+ name: 'Grafana Labs',
+ url: 'https://grafana.com',
+ },
+ description: 'Placeholder for the distributed tracing system.',
+ links: [
+ {
+ name: 'Learn more',
+ url: 'https://zipkin.io',
+ },
+ {
+ name: 'Raise issue',
+ url: 'https://github.com/grafana/grafana/issues/new',
+ },
+ {
+ name: 'Documentation',
+ url: 'https://grafana.com/docs/grafana/latest/datasources/zipkin/',
+ },
+ ],
+ },
+ dependencies: {
+ grafanaDependency: '>=10.3.0-0',
+ grafanaVersion: '*',
+ },
+ backend: true,
+ category: 'tracing',
+ executable: 'gpx_zipkin',
+ metrics: true,
+ tracing: true,
+ },
+ class: 'core',
+ module: {
+ path: 'public/plugins/zipkin/module.js',
+ loadingStrategy: 'script',
+ },
+ baseURL: 'public/plugins/zipkin',
+ signature: {
+ status: 'internal',
+ },
+ angular: {
+ detected: false,
+ },
+ },
+ status: {},
+ },
+ ],
+});
diff --git a/packages/grafana-runtime/src/services/pluginMeta/types.ts b/packages/grafana-runtime/src/services/pluginMeta/types.ts
new file mode 100644
index 00000000000..81efe0df7b3
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/types.ts
@@ -0,0 +1,10 @@
+import type { AppPluginConfig } from '@grafana/data';
+
+import type { Meta } from './types/meta_object_gen';
+
+export type AppPluginMetas = Record;
+
+export type AppPluginMetasMapper = (response: T) => AppPluginMetas;
+export interface PluginMetasResponse {
+ items: Meta[];
+}
diff --git a/packages/grafana-runtime/src/services/pluginMeta/types/meta_object_gen.ts b/packages/grafana-runtime/src/services/pluginMeta/types/meta_object_gen.ts
new file mode 100644
index 00000000000..044ec1f4cd8
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/types/meta_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 Meta {
+ kind: string;
+ apiVersion: string;
+ metadata: Metadata;
+ spec: Spec;
+ status: Status;
+}
diff --git a/packages/grafana-runtime/src/services/pluginMeta/types/types.spec.gen.ts b/packages/grafana-runtime/src/services/pluginMeta/types/types.spec.gen.ts
new file mode 100644
index 00000000000..51845e98454
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/types/types.spec.gen.ts
@@ -0,0 +1,278 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+// JSON configuration schema for Grafana plugins
+// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json
+export interface JSONData {
+ // Unique name of the plugin
+ id: string;
+ // Plugin type
+ type: "app" | "datasource" | "panel" | "renderer";
+ // Human-readable name of the plugin
+ name: string;
+ // Metadata for the plugin
+ info: Info;
+ // Dependency information
+ dependencies: Dependencies;
+ // Optional fields
+ alerting?: boolean;
+ annotations?: boolean;
+ autoEnabled?: boolean;
+ backend?: boolean;
+ buildMode?: string;
+ builtIn?: boolean;
+ category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other";
+ enterpriseFeatures?: EnterpriseFeatures;
+ executable?: string;
+ hideFromList?: boolean;
+ // +listType=atomic
+ includes?: Include[];
+ logs?: boolean;
+ metrics?: boolean;
+ multiValueFilterOperators?: boolean;
+ pascalName?: string;
+ preload?: boolean;
+ queryOptions?: QueryOptions;
+ // +listType=atomic
+ routes?: Route[];
+ skipDataQuery?: boolean;
+ state?: "alpha" | "beta";
+ streaming?: boolean;
+ suggestions?: boolean;
+ tracing?: boolean;
+ iam?: IAM;
+ // +listType=atomic
+ roles?: Role[];
+ extensions?: Extensions;
+}
+
+export const defaultJSONData = (): JSONData => ({
+ id: "",
+ type: "app",
+ name: "",
+ info: defaultInfo(),
+ dependencies: defaultDependencies(),
+});
+
+export interface Info {
+ // Required fields
+ // +listType=set
+ keywords: string[];
+ logos: {
+ small: string;
+ large: string;
+ };
+ updated: string;
+ version: string;
+ // Optional fields
+ author?: {
+ name?: string;
+ email?: string;
+ url?: string;
+ };
+ description?: string;
+ // +listType=atomic
+ links?: {
+ name?: string;
+ url?: string;
+ }[];
+ // +listType=atomic
+ screenshots?: {
+ name?: string;
+ path?: string;
+ }[];
+}
+
+export const defaultInfo = (): Info => ({
+ keywords: [],
+ logos: {
+ small: "",
+ large: "",
+},
+ updated: "",
+ version: "",
+});
+
+export interface Dependencies {
+ // Required field
+ grafanaDependency: string;
+ // Optional fields
+ grafanaVersion?: string;
+ // +listType=set
+ // +listMapKey=id
+ plugins?: {
+ id: string;
+ type: "app" | "datasource" | "panel";
+ name: string;
+ }[];
+ extensions?: {
+ // +listType=set
+ exposedComponents?: string[];
+ };
+}
+
+export const defaultDependencies = (): Dependencies => ({
+ grafanaDependency: "",
+});
+
+export interface EnterpriseFeatures {
+ // Allow additional properties
+ healthDiagnosticsErrors?: boolean;
+}
+
+export const defaultEnterpriseFeatures = (): EnterpriseFeatures => ({
+ healthDiagnosticsErrors: false,
+});
+
+export interface Include {
+ uid?: string;
+ type?: "dashboard" | "page" | "panel" | "datasource";
+ name?: string;
+ component?: string;
+ role?: "Admin" | "Editor" | "Viewer" | "None";
+ action?: string;
+ path?: string;
+ addToNav?: boolean;
+ defaultNav?: boolean;
+ icon?: string;
+}
+
+export const defaultInclude = (): Include => ({
+});
+
+export interface QueryOptions {
+ maxDataPoints?: boolean;
+ minInterval?: boolean;
+ cacheTimeout?: boolean;
+}
+
+export const defaultQueryOptions = (): QueryOptions => ({
+});
+
+export interface Route {
+ path?: string;
+ method?: string;
+ url?: string;
+ reqSignedIn?: boolean;
+ reqRole?: string;
+ reqAction?: string;
+ // +listType=atomic
+ headers?: string[];
+ body?: Record;
+ tokenAuth?: {
+ url?: string;
+ // +listType=set
+ scopes?: string[];
+ params?: Record;
+ };
+ jwtTokenAuth?: {
+ url?: string;
+ // +listType=set
+ scopes?: string[];
+ params?: Record;
+ };
+ // +listType=atomic
+ urlParams?: {
+ name?: string;
+ content?: string;
+ }[];
+}
+
+export const defaultRoute = (): Route => ({
+});
+
+export interface IAM {
+ // +listType=atomic
+ permissions?: {
+ action?: string;
+ scope?: string;
+ }[];
+}
+
+export const defaultIAM = (): IAM => ({
+});
+
+export interface Role {
+ role?: {
+ name?: string;
+ description?: string;
+ // +listType=atomic
+ permissions?: {
+ action?: string;
+ scope?: string;
+ }[];
+ };
+ // +listType=set
+ grants?: string[];
+}
+
+export const defaultRole = (): Role => ({
+});
+
+export interface Extensions {
+ // +listType=atomic
+ addedComponents?: {
+ // +listType=set
+ targets: string[];
+ title: string;
+ description?: string;
+ }[];
+ // +listType=atomic
+ addedLinks?: {
+ // +listType=set
+ targets: string[];
+ title: string;
+ description?: string;
+ }[];
+ // +listType=atomic
+ addedFunctions?: {
+ // +listType=set
+ targets: string[];
+ title: string;
+ description?: string;
+ }[];
+ // +listType=set
+ // +listMapKey=id
+ exposedComponents?: {
+ id: string;
+ title?: string;
+ description?: string;
+ }[];
+ // +listType=set
+ // +listMapKey=id
+ extensionPoints?: {
+ id: string;
+ title?: string;
+ description?: string;
+ }[];
+}
+
+export const defaultExtensions = (): Extensions => ({
+});
+
+export interface Spec {
+ pluginJson: JSONData;
+ class: "core" | "external";
+ module?: {
+ path: string;
+ hash?: string;
+ loadingStrategy?: "fetch" | "script";
+ };
+ baseURL?: string;
+ signature?: {
+ status: "internal" | "valid" | "invalid" | "modified" | "unsigned";
+ type?: "grafana" | "commercial" | "community" | "private" | "private-glob";
+ org?: string;
+ };
+ angular?: {
+ detected: boolean;
+ };
+ translations?: Record;
+ // +listType=atomic
+ children?: string[];
+}
+
+export const defaultSpec = (): Spec => ({
+ pluginJson: defaultJSONData(),
+ class: "core",
+});
+
diff --git a/packages/grafana-runtime/src/services/pluginMeta/types/types.status.gen.ts b/packages/grafana-runtime/src/services/pluginMeta/types/types.status.gen.ts
new file mode 100644
index 00000000000..01be8df7961
--- /dev/null
+++ b/packages/grafana-runtime/src/services/pluginMeta/types/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/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 8d06591b46b..1627b2dc29b 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.4.0-pre";
+export const pluginVersion = "%VERSION%";
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);
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 c0f8481a7f5..daead8f5295 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
@@ -29,11 +29,14 @@ export interface Options extends common.SingleStatBaseOptions {
barWidthFactor: number;
effects: GaugePanelEffects;
endpointMarker?: ('point' | 'glow' | 'none');
+ minVizHeight: number;
+ minVizWidth: number;
segmentCount: number;
segmentSpacing: number;
shape: ('circle' | 'gauge');
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
+ sizing: common.BarGaugeSizing;
sparkline?: boolean;
textMode?: ('auto' | 'value_and_name' | 'value' | 'name' | 'none');
}
@@ -43,11 +46,14 @@ export const defaultOptions: Partial = {
barWidthFactor: 0.5,
effects: {},
endpointMarker: 'point',
+ minVizHeight: 75,
+ minVizWidth: 75,
segmentCount: 1,
segmentSpacing: 0.3,
shape: 'gauge',
showThresholdLabels: false,
showThresholdMarkers: true,
+ sizing: common.BarGaugeSizing.Auto,
sparkline: true,
textMode: 'auto',
};
diff --git a/packages/grafana-test-utils/src/fixtures/scopes.ts b/packages/grafana-test-utils/src/fixtures/scopes.ts
new file mode 100644
index 00000000000..1d7c6ee0143
--- /dev/null
+++ b/packages/grafana-test-utils/src/fixtures/scopes.ts
@@ -0,0 +1,500 @@
+/**
+ * Types for Scopes API - matching @grafana/data types
+ */
+
+export interface ScopeFilter {
+ key: string;
+ value: string;
+ operator: 'equals' | 'not-equals' | 'regex-match' | 'regex-not-match';
+}
+
+export interface ScopeSpec {
+ title: string;
+ filters: ScopeFilter[];
+}
+
+export interface Scope {
+ metadata: {
+ name: string;
+ };
+ spec: ScopeSpec;
+}
+
+export interface ScopeNodeSpec {
+ nodeType: 'container' | 'leaf';
+ title: string;
+ description?: string;
+ disableMultiSelect?: boolean;
+ linkType?: 'scope';
+ linkId?: string;
+ parentName: string;
+}
+
+export interface ScopeNode {
+ metadata: {
+ name: string;
+ };
+ spec: ScopeNodeSpec;
+}
+
+export interface ScopeDashboardBindingSpec {
+ dashboard: string;
+ scope: string;
+}
+
+export interface ScopeDashboardBindingStatus {
+ dashboardTitle: string;
+ groups?: string[];
+}
+
+export interface ScopeDashboardBinding {
+ metadata: {
+ name: string;
+ };
+ spec: ScopeDashboardBindingSpec;
+ status: ScopeDashboardBindingStatus;
+}
+
+export interface ScopeNavigation {
+ metadata: {
+ name: string;
+ };
+ spec: {
+ url: string;
+ scope: string;
+ subScope?: string;
+ preLoadSubScopeChildren?: boolean;
+ expandOnLoad?: boolean;
+ disableSubScopeSelection?: boolean;
+ };
+ status: {
+ title: string;
+ groups?: string[];
+ };
+}
+
+export const MOCK_SCOPES: Scope[] = [
+ {
+ metadata: { name: 'cloud' },
+ spec: {
+ title: 'Cloud',
+ filters: [{ key: 'cloud', value: '.*', operator: 'regex-match' }],
+ },
+ },
+ {
+ metadata: { name: 'dev' },
+ spec: {
+ title: 'Dev',
+ filters: [{ key: 'cloud', value: 'dev', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'ops' },
+ spec: {
+ title: 'Ops',
+ filters: [{ key: 'cloud', value: 'ops', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'prod' },
+ spec: {
+ title: 'Prod',
+ filters: [{ key: 'cloud', value: 'prod', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'grafana' },
+ spec: {
+ title: 'Grafana',
+ filters: [{ key: 'app', value: 'grafana', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'mimir' },
+ spec: {
+ title: 'Mimir',
+ filters: [{ key: 'app', value: 'mimir', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'loki' },
+ spec: {
+ title: 'Loki',
+ filters: [{ key: 'app', value: 'loki', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'tempo' },
+ spec: {
+ title: 'Tempo',
+ filters: [{ key: 'app', value: 'tempo', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'dev-env' },
+ spec: {
+ title: 'Development',
+ filters: [{ key: 'environment', value: 'dev', operator: 'equals' }],
+ },
+ },
+ {
+ metadata: { name: 'prod-env' },
+ spec: {
+ title: 'Production',
+ filters: [{ key: 'environment', value: 'prod', operator: 'equals' }],
+ },
+ },
+];
+
+const dashboardBindingsGenerator = (
+ scopes: string[],
+ dashboards: Array<{ dashboardTitle: string; dashboardKey?: string; groups?: string[] }>
+) =>
+ scopes.reduce((scopeAcc, scopeTitle) => {
+ const scope = scopeTitle.toLowerCase().replaceAll(' ', '-').replaceAll('/', '-');
+
+ return [
+ ...scopeAcc,
+ ...dashboards.reduce((acc, { dashboardTitle, groups, dashboardKey }, idx) => {
+ dashboardKey = dashboardKey ?? dashboardTitle.toLowerCase().replaceAll(' ', '-').replaceAll('/', '-');
+ const group = !groups
+ ? ''
+ : groups.length === 1
+ ? groups[0] === ''
+ ? ''
+ : `${groups[0].toLowerCase().replaceAll(' ', '-').replaceAll('/', '-')}-`
+ : `multiple${idx}-`;
+ const dashboard = `${group}${dashboardKey}`;
+
+ return [
+ ...acc,
+ {
+ metadata: { name: `${scope}-${dashboard}` },
+ spec: {
+ dashboard,
+ scope,
+ },
+ status: {
+ dashboardTitle,
+ groups,
+ },
+ },
+ ];
+ }, []),
+ ];
+ }, []);
+
+export const MOCK_SCOPE_DASHBOARD_BINDINGS: ScopeDashboardBinding[] = [
+ ...dashboardBindingsGenerator(
+ ['Grafana'],
+ [
+ { dashboardTitle: 'Data Sources', groups: ['General'] },
+ { dashboardTitle: 'Usage', groups: ['General'] },
+ { dashboardTitle: 'Frontend Errors', groups: ['Observability'] },
+ { dashboardTitle: 'Frontend Logs', groups: ['Observability'] },
+ { dashboardTitle: 'Backend Errors', groups: ['Observability'] },
+ { dashboardTitle: 'Backend Logs', groups: ['Observability'] },
+ { dashboardTitle: 'Usage Overview', groups: ['Usage'] },
+ { dashboardTitle: 'Data Sources', groups: ['Usage'] },
+ { dashboardTitle: 'Stats', groups: ['Usage'] },
+ { dashboardTitle: 'Overview', groups: [''] },
+ { dashboardTitle: 'Frontend' },
+ { dashboardTitle: 'Stats' },
+ ]
+ ),
+ ...dashboardBindingsGenerator(
+ ['Loki', 'Tempo', 'Mimir'],
+ [
+ { dashboardTitle: 'Ingester', groups: ['Components', 'Investigations'] },
+ { dashboardTitle: 'Distributor', groups: ['Components', 'Investigations'] },
+ { dashboardTitle: 'Compacter', groups: ['Components', 'Investigations'] },
+ { dashboardTitle: 'Datasource Errors', groups: ['Observability', 'Investigations'] },
+ { dashboardTitle: 'Datasource Logs', groups: ['Observability', 'Investigations'] },
+ { dashboardTitle: 'Overview' },
+ { dashboardTitle: 'Stats', dashboardKey: 'another-stats' },
+ ]
+ ),
+ ...dashboardBindingsGenerator(
+ ['Dev', 'Ops', 'Prod'],
+ [
+ { dashboardTitle: 'Overview', groups: ['Cardinality Management'] },
+ { dashboardTitle: 'Metrics', groups: ['Cardinality Management'] },
+ { dashboardTitle: 'Labels', groups: ['Cardinality Management'] },
+ { dashboardTitle: 'Overview', groups: ['Usage Insights'] },
+ { dashboardTitle: 'Data Sources', groups: ['Usage Insights'] },
+ { dashboardTitle: 'Query Errors', groups: ['Usage Insights'] },
+ { dashboardTitle: 'Alertmanager', groups: ['Usage Insights'] },
+ { dashboardTitle: 'Metrics Ingestion', groups: ['Usage Insights'] },
+ { dashboardTitle: 'Billing/Usage' },
+ ]
+ ),
+];
+
+export const MOCK_NODES: ScopeNode[] = [
+ {
+ metadata: { name: 'applications' },
+ spec: {
+ nodeType: 'container',
+ title: 'Applications',
+ description: 'Application Scopes',
+ parentName: '',
+ },
+ },
+ {
+ metadata: { name: 'cloud' },
+ spec: {
+ nodeType: 'container',
+ title: 'Cloud',
+ description: 'Cloud Scopes',
+ disableMultiSelect: true,
+ linkType: 'scope',
+ linkId: 'cloud',
+ parentName: '',
+ },
+ },
+ {
+ metadata: { name: 'applications-grafana' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Grafana',
+ description: 'Grafana',
+ linkType: 'scope',
+ linkId: 'grafana',
+ parentName: 'applications',
+ },
+ },
+ {
+ metadata: { name: 'applications-mimir' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Mimir',
+ description: 'Mimir',
+ linkType: 'scope',
+ linkId: 'mimir',
+ parentName: 'applications',
+ },
+ },
+ {
+ metadata: { name: 'applications-loki' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Loki',
+ description: 'Loki',
+ linkType: 'scope',
+ linkId: 'loki',
+ parentName: 'applications',
+ },
+ },
+ {
+ metadata: { name: 'applications-tempo' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Tempo',
+ description: 'Tempo',
+ linkType: 'scope',
+ linkId: 'tempo',
+ parentName: 'applications',
+ },
+ },
+ {
+ metadata: { name: 'applications-cloud' },
+ spec: {
+ nodeType: 'container',
+ title: 'Cloud',
+ description: 'Application/Cloud Scopes',
+ linkType: 'scope',
+ linkId: 'cloud',
+ parentName: 'applications',
+ },
+ },
+ {
+ metadata: { name: 'applications-cloud-dev' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Dev',
+ description: 'Dev',
+ linkType: 'scope',
+ linkId: 'dev',
+ parentName: 'applications-cloud',
+ },
+ },
+ {
+ metadata: { name: 'applications-cloud-ops' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Ops',
+ description: 'Ops',
+ linkType: 'scope',
+ linkId: 'ops',
+ parentName: 'applications-cloud',
+ },
+ },
+ {
+ metadata: { name: 'applications-cloud-prod' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Prod',
+ description: 'Prod',
+ linkType: 'scope',
+ linkId: 'prod',
+ parentName: 'applications-cloud',
+ },
+ },
+ {
+ metadata: { name: 'cloud-dev' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Dev',
+ description: 'Dev',
+ linkType: 'scope',
+ linkId: 'dev',
+ parentName: 'cloud',
+ },
+ },
+ {
+ metadata: { name: 'cloud-ops' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Ops',
+ description: 'Ops',
+ linkType: 'scope',
+ linkId: 'ops',
+ parentName: 'cloud',
+ },
+ },
+ {
+ metadata: { name: 'cloud-prod' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Prod',
+ description: 'Prod',
+ linkType: 'scope',
+ linkId: 'prod',
+ parentName: 'cloud',
+ },
+ },
+ {
+ metadata: { name: 'cloud-applications' },
+ spec: {
+ nodeType: 'container',
+ title: 'Applications',
+ description: 'Cloud/Application Scopes',
+ parentName: 'cloud',
+ },
+ },
+ {
+ metadata: { name: 'cloud-applications-grafana' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Grafana',
+ description: 'Grafana',
+ linkType: 'scope',
+ linkId: 'grafana',
+ parentName: 'cloud-applications',
+ },
+ },
+ {
+ metadata: { name: 'cloud-applications-mimir' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Mimir',
+ description: 'Mimir',
+ linkType: 'scope',
+ linkId: 'mimir',
+ parentName: 'cloud-applications',
+ },
+ },
+ {
+ metadata: { name: 'cloud-applications-loki' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Loki',
+ description: 'Loki',
+ linkType: 'scope',
+ linkId: 'loki',
+ parentName: 'cloud-applications',
+ },
+ },
+ {
+ metadata: { name: 'cloud-applications-tempo' },
+ spec: {
+ nodeType: 'leaf',
+ title: 'Tempo',
+ description: 'Tempo',
+ linkType: 'scope',
+ linkId: 'tempo',
+ parentName: 'cloud-applications',
+ },
+ },
+ {
+ metadata: { name: 'environments' },
+ spec: {
+ nodeType: 'container',
+ title: 'Environments',
+ description: 'Environment Scopes',
+ disableMultiSelect: true,
+ parentName: '',
+ },
+ },
+ {
+ metadata: { name: 'environments-dev' },
+ spec: {
+ nodeType: 'container',
+ title: 'Development',
+ description: 'Development Environment',
+ linkType: 'scope',
+ linkId: 'dev-env',
+ parentName: 'environments',
+ },
+ },
+ {
+ metadata: { name: 'environments-prod' },
+ spec: {
+ nodeType: 'container',
+ title: 'Production',
+ description: 'Production Environment',
+ linkType: 'scope',
+ linkId: 'prod-env',
+ parentName: 'environments',
+ },
+ },
+];
+
+export const MOCK_SUB_SCOPE_MIMIR_ITEMS: ScopeNavigation[] = [
+ {
+ metadata: { name: 'mimir-item-1' },
+ spec: {
+ scope: 'mimir',
+ url: '/d/mimir-dashboard-1',
+ },
+ status: {
+ title: 'Mimir Dashboard 1',
+ groups: ['General'],
+ },
+ },
+ {
+ metadata: { name: 'mimir-item-2' },
+ spec: {
+ scope: 'mimir',
+ url: '/d/mimir-dashboard-2',
+ },
+ status: {
+ title: 'Mimir Dashboard 2',
+ groups: ['Observability'],
+ },
+ },
+];
+
+export const MOCK_SUB_SCOPE_LOKI_ITEMS: ScopeNavigation[] = [
+ {
+ metadata: { name: 'loki-item-1' },
+ spec: {
+ scope: 'loki',
+ url: '/d/loki-dashboard-1',
+ },
+ status: {
+ title: 'Loki Dashboard 1',
+ groups: ['General'],
+ },
+ },
+];
diff --git a/packages/grafana-test-utils/src/handlers/all-handlers.ts b/packages/grafana-test-utils/src/handlers/all-handlers.ts
index 5fa473b55d5..83a34d7455f 100644
--- a/packages/grafana-test-utils/src/handlers/all-handlers.ts
+++ b/packages/grafana-test-utils/src/handlers/all-handlers.ts
@@ -12,6 +12,7 @@ import appPlatformDashboardv0alpha1Handlers from './apis/dashboard.grafana.app/v
import appPlatformDashboardv1beta1Handlers from './apis/dashboard.grafana.app/v1beta1/handlers';
import appPlatformFolderv1beta1Handlers from './apis/folder.grafana.app/v1beta1/handlers';
import appPlatformIamv0alpha1Handlers from './apis/iam.grafana.app/v0alpha1/handlers';
+import appPlatformScopev0alpha1Handlers from './apis/scope.grafana.app/v0alpha1/handlers';
const allHandlers: HttpHandler[] = [
// Legacy handlers
@@ -29,6 +30,7 @@ const allHandlers: HttpHandler[] = [
...appPlatformFolderv1beta1Handlers,
...appPlatformIamv0alpha1Handlers,
...appPlatformCollectionsv1alpha1Handlers,
+ ...appPlatformScopev0alpha1Handlers,
];
export default allHandlers;
diff --git a/packages/grafana-test-utils/src/handlers/apis/scope.grafana.app/v0alpha1/handlers.ts b/packages/grafana-test-utils/src/handlers/apis/scope.grafana.app/v0alpha1/handlers.ts
new file mode 100644
index 00000000000..098548caad7
--- /dev/null
+++ b/packages/grafana-test-utils/src/handlers/apis/scope.grafana.app/v0alpha1/handlers.ts
@@ -0,0 +1,131 @@
+import { HttpResponse, http } from 'msw';
+
+import {
+ MOCK_NODES,
+ MOCK_SCOPES,
+ MOCK_SCOPE_DASHBOARD_BINDINGS,
+ MOCK_SUB_SCOPE_LOKI_ITEMS,
+ MOCK_SUB_SCOPE_MIMIR_ITEMS,
+ ScopeNavigation,
+} from '../../../../fixtures/scopes';
+import { getErrorResponse } from '../../../helpers';
+
+const API_BASE = '/apis/scope.grafana.app/v0alpha1/namespaces/:namespace';
+
+/**
+ * GET /apis/scope.grafana.app/v0alpha1/namespaces/:namespace/scopes/:name
+ *
+ * Fetches a single scope by name.
+ */
+const getScopeHandler = () =>
+ http.get<{ namespace: string; name: string }>(`${API_BASE}/scopes/:name`, ({ params }) => {
+ const { name } = params;
+ const scope = MOCK_SCOPES.find((s) => s.metadata.name === name);
+
+ if (!scope) {
+ return HttpResponse.json(getErrorResponse(`scopes.scope.grafana.app "${name}" not found`, 404), {
+ status: 404,
+ });
+ }
+
+ return HttpResponse.json(scope);
+ });
+
+/**
+ * GET /apis/scope.grafana.app/v0alpha1/namespaces/:namespace/scopenodes/:name
+ *
+ * Fetches a single scope node by name.
+ */
+const getScopeNodeHandler = () =>
+ http.get<{ namespace: string; name: string }>(`${API_BASE}/scopenodes/:name`, ({ params }) => {
+ const { name } = params;
+ const node = MOCK_NODES.find((n) => n.metadata.name === name);
+
+ if (!node) {
+ return HttpResponse.json(getErrorResponse(`scopenodes.scope.grafana.app "${name}" not found`, 404), {
+ status: 404,
+ });
+ }
+
+ return HttpResponse.json(node);
+ });
+
+/**
+ * GET /apis/scope.grafana.app/v0alpha1/namespaces/:namespace/find/scope_node_children
+ *
+ * Finds scope node children based on parent and query filters.
+ */
+const findScopeNodeChildrenHandler = () =>
+ http.get(`${API_BASE}/find/scope_node_children`, ({ request }) => {
+ const url = new URL(request.url);
+ const parent = url.searchParams.get('parent') ?? '';
+ const query = url.searchParams.get('query') ?? '';
+ const limitParam = url.searchParams.get('limit');
+ const names = url.searchParams.getAll('names');
+
+ let filtered = MOCK_NODES.filter(
+ (node) => node.spec.parentName === parent && node.spec.title.toLowerCase().includes(query.toLowerCase())
+ );
+
+ if (names.length > 0) {
+ filtered = MOCK_NODES.filter((node) => names.includes(node.metadata.name));
+ }
+
+ if (limitParam) {
+ const limit = parseInt(limitParam, 10);
+ filtered = filtered.slice(0, limit);
+ }
+
+ return HttpResponse.json({
+ items: filtered,
+ });
+ });
+
+/**
+ * GET /apis/scope.grafana.app/v0alpha1/namespaces/:namespace/find/scope_dashboard_bindings
+ *
+ * Finds scope dashboard bindings for the given scope names.
+ */
+const findScopeDashboardBindingsHandler = () =>
+ http.get(`${API_BASE}/find/scope_dashboard_bindings`, ({ request }) => {
+ const url = new URL(request.url);
+ const scopeNames = url.searchParams.getAll('scope');
+
+ const bindings = MOCK_SCOPE_DASHBOARD_BINDINGS.filter((b) => scopeNames.includes(b.spec.scope));
+
+ return HttpResponse.json({
+ items: bindings,
+ });
+ });
+
+/**
+ * GET /apis/scope.grafana.app/v0alpha1/namespaces/:namespace/find/scope_navigations
+ *
+ * Finds scope navigations for the given scope names.
+ */
+const findScopeNavigationsHandler = () =>
+ http.get(`${API_BASE}/find/scope_navigations`, ({ request }) => {
+ const url = new URL(request.url);
+ const scopeNames = url.searchParams.getAll('scope');
+
+ let items: ScopeNavigation[] = [];
+
+ if (scopeNames.includes('mimir')) {
+ items = [...items, ...MOCK_SUB_SCOPE_MIMIR_ITEMS];
+ }
+ if (scopeNames.includes('loki')) {
+ items = [...items, ...MOCK_SUB_SCOPE_LOKI_ITEMS];
+ }
+
+ return HttpResponse.json({
+ items,
+ });
+ });
+
+export default [
+ getScopeHandler(),
+ getScopeNodeHandler(),
+ findScopeNodeChildrenHandler(),
+ findScopeDashboardBindingsHandler(),
+ findScopeNavigationsHandler(),
+];
diff --git a/packages/grafana-test-utils/src/unstable.ts b/packages/grafana-test-utils/src/unstable.ts
index d03bc685d9e..698d57a774c 100644
--- a/packages/grafana-test-utils/src/unstable.ts
+++ b/packages/grafana-test-utils/src/unstable.ts
@@ -2,3 +2,12 @@ import { wellFormedTree } from './fixtures/folders';
export const getFolderFixtures = wellFormedTree;
export { MOCK_TEAMS, MOCK_TEAM_GROUPS } from './fixtures/teams';
+export {
+ MOCK_SCOPES,
+ MOCK_NODES,
+ MOCK_SCOPE_DASHBOARD_BINDINGS,
+ MOCK_SUB_SCOPE_MIMIR_ITEMS,
+ MOCK_SUB_SCOPE_LOKI_ITEMS,
+} from './fixtures/scopes';
+export { default as allHandlers } from './handlers/all-handlers';
+export { default as scopeHandlers } from './handlers/apis/scope.grafana.app/v0alpha1/handlers';
diff --git a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx
index a10ad157120..5b919c05ec2 100644
--- a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx
+++ b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx
@@ -14,6 +14,8 @@ export type Props = React.ComponentProps & {
isConfigured: boolean;
/** Called when the user clicks on the "Reset" button in order to clear the secret */
onReset: () => void;
+ /** If true, the text area will grow to fill available width. */
+ grow?: boolean;
};
export const CONFIGURED_TEXT = 'configured';
@@ -35,11 +37,11 @@ const getStyles = (theme: GrafanaTheme2) => {
*
* https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-secrettextarea--docs
*/
-export const SecretTextArea = ({ isConfigured, onReset, ...props }: Props) => {
+export const SecretTextArea = ({ isConfigured, onReset, grow, ...props }: Props) => {
const styles = useStyles2(getStyles);
return (
-
+
{!isConfigured && }
{isConfigured && (