mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transform DashboardScene into Schema V2 (#95546)
--------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com> Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
This commit is contained in:
parent
77a1b0ecce
commit
3c182a37fa
@ -2431,6 +2431,14 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "8"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
@ -2561,9 +2569,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "3"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "4"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/sharing/ShareExportTab.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -672,6 +672,7 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
||||
# Kind definitions
|
||||
/kinds/dashboard @grafana/dashboards-squad
|
||||
/kinds/ @grafana/grafana-as-code
|
||||
kindsv2/ @grafana/dashboards-squad
|
||||
|
||||
# Kind system and code generation
|
||||
embed.go @grafana/grafana-as-code
|
||||
|
@ -77,6 +77,7 @@ RUN if [[ "$BINGO" = "true" ]]; then \
|
||||
COPY embed.go Makefile build.go package.json ./
|
||||
COPY cue.mod cue.mod
|
||||
COPY kinds kinds
|
||||
COPY kindsv2 kindsv2
|
||||
COPY local local
|
||||
COPY packages/grafana-schema packages/grafana-schema
|
||||
COPY public/app/plugins public/app/plugins
|
||||
|
5
Makefile
5
Makefile
@ -146,6 +146,11 @@ gen-cue: ## Do all CUE/Thema code generation
|
||||
go generate ./kinds/gen.go
|
||||
go generate ./public/app/plugins/gen.go
|
||||
|
||||
.PHONY: gen-cuev2
|
||||
gen-cuev2: ## Do all CUE code generation
|
||||
@echo "generate code from .cue files (v2)"
|
||||
go generate ./kindsv2/gen.go
|
||||
|
||||
.PHONY: gen-feature-toggles
|
||||
gen-feature-toggles:
|
||||
## First go test run fails because it will re-generate the feature toggles.
|
||||
|
10
go.mod
10
go.mod
@ -16,7 +16,7 @@ require (
|
||||
cloud.google.com/go/kms v1.18.5 // @grafana/grafana-backend-group
|
||||
cloud.google.com/go/storage v1.43.0 // @grafana/grafana-backend-group
|
||||
connectrpc.com/connect v1.17.0 // @grafana/observability-traces-and-profiling
|
||||
cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code
|
||||
cuelang.org/go v0.8.2 // @grafana/grafana-as-code
|
||||
filippo.io/age v1.1.1 // @grafana/identity-access-team
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // @grafana/grafana-backend-group
|
||||
@ -75,7 +75,7 @@ require (
|
||||
github.com/grafana/alerting v0.0.0-20241021123319-be61d61f71e7 // @grafana/alerting-backend
|
||||
github.com/grafana/authlib v0.0.0-20241024120339-84cd3a898e8a // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/claims v0.0.0-20241024115517-d30b00d7666d // @grafana/identity-access-team
|
||||
github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d // @grafana/dataviz-squad
|
||||
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
|
||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
|
||||
@ -109,7 +109,7 @@ require (
|
||||
github.com/hashicorp/go-version v1.7.0 // @grafana/grafana-backend-group
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // @grafana/alerting-backend
|
||||
github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-backend
|
||||
github.com/huandu/xstrings v1.3.3 // @grafana/partner-datasources
|
||||
github.com/huandu/xstrings v1.5.0 // @grafana/partner-datasources
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.13.0 // @grafana/observability-metrics
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect; @grafana/grafana-backend-group
|
||||
@ -266,7 +266,7 @@ require (
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emicklei/proto v1.10.0 // indirect
|
||||
github.com/emicklei/proto v1.13.2 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@ -382,7 +382,7 @@ require (
|
||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.11.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 // indirect
|
||||
github.com/redis/rueidis v1.0.45 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
|
15
go.sum
15
go.sum
@ -1833,8 +1833,8 @@ github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8
|
||||
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
|
||||
github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
|
||||
github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
@ -2266,8 +2266,8 @@ github.com/grafana/authlib v0.0.0-20241024120339-84cd3a898e8a h1:X3mroOOXdryRY6S
|
||||
github.com/grafana/authlib v0.0.0-20241024120339-84cd3a898e8a/go.mod h1:XFhcSCEDeOgV43x41we7mRBcizDpsTScb7XOuYipQZg=
|
||||
github.com/grafana/authlib/claims v0.0.0-20241024115517-d30b00d7666d h1:7nZfaXdC4Xc2ocMz5/Bx/3EsaEO34KsmA2RRcuogLnc=
|
||||
github.com/grafana/authlib/claims v0.0.0-20241024115517-d30b00d7666d/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
|
||||
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
||||
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
|
||||
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws=
|
||||
github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk=
|
||||
@ -2470,8 +2470,9 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
@ -3034,8 +3035,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/prometheus v0.52.0 h1:f7kHJgr7+zShpWdTCeKqbCWR7nKTScgLYQwRux9h1V0=
|
||||
github.com/prometheus/prometheus v0.52.0/go.mod h1:3z74cVsmVH0iXOR5QBjB7Pa6A0KJeEAK5A6UsmAFb1g=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 h1:MAzmm+JtFxQwTPb1cVMLkemw2OxLy5AB/d/rxtAwGQQ=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
|
1
go.work
1
go.work
@ -6,6 +6,7 @@ go 1.23.1
|
||||
use (
|
||||
. // skip:golangci-lint
|
||||
./apps/playlist
|
||||
./kindsv2
|
||||
./pkg/aggregator
|
||||
./pkg/apimachinery
|
||||
./pkg/apiserver
|
||||
|
52
kindsv2/gen.go
Normal file
52
kindsv2/gen.go
Normal file
@ -0,0 +1,52 @@
|
||||
//go:generate go run gen.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/grafana/cog"
|
||||
)
|
||||
|
||||
type codegenTargets struct {
|
||||
schemaPath string
|
||||
outputPath string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cueCtx := cuecontext.New()
|
||||
|
||||
targets := []codegenTargets{
|
||||
{
|
||||
schemaPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue",
|
||||
outputPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts",
|
||||
},
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
rawSchema, err := os.ReadFile(target.schemaPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
value := cueCtx.CompileBytes(rawSchema)
|
||||
if value.Err() != nil {
|
||||
panic(value.Err())
|
||||
}
|
||||
|
||||
codegenPipeline := cog.TypesFromSchema().
|
||||
CUEValue("dashboard", value).
|
||||
Typescript()
|
||||
|
||||
tsBytes, err := codegenPipeline.Run(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(target.outputPath, tsBytes, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
46
kindsv2/go.mod
Normal file
46
kindsv2/go.mod
Normal file
@ -0,0 +1,46 @@
|
||||
module github.com/grafana/grafana/kindsv2
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.8.2
|
||||
github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070
|
||||
)
|
||||
|
||||
require (
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/proto v1.13.2 // indirect
|
||||
github.com/expr-lang/expr v1.16.9 // indirect
|
||||
github.com/getkin/kin-openapi v0.128.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/invopop/yaml v0.3.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/yalue/merged_fs v1.3.0 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
94
kindsv2/go.sum
Normal file
94
kindsv2/go.sum
Normal file
@ -0,0 +1,94 @@
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg=
|
||||
cuelang.org/go v0.8.2 h1:vWfHI1kQlBvwkna7ktAqXjV5LUEAgU6vyMlJjvZZaDw=
|
||||
cuelang.org/go v0.8.2/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
|
||||
github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
|
||||
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4=
|
||||
github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
||||
github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070 h1:ncAkFmIq3UbPiPhQ2CuBzdTuwnDqLsFFNqEIXKaiPGA=
|
||||
github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070/go.mod h1:FqZi9WZ/Uzvs3tvo7l+OViThCpfvu3KDGDqHCB2LNbg=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 h1:MAzmm+JtFxQwTPb1cVMLkemw2OxLy5AB/d/rxtAwGQQ=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY=
|
||||
github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,785 @@
|
||||
package dashboard
|
||||
|
||||
DashboardV2Spec: {
|
||||
// Unique numeric identifier for the dashboard.
|
||||
// `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances.
|
||||
id?: int64
|
||||
|
||||
// Title of dashboard.
|
||||
title: string
|
||||
|
||||
// Description of dashboard.
|
||||
description?: string
|
||||
|
||||
// Configuration of dashboard cursor sync behavior.
|
||||
// "Off" for no shared crosshair or tooltip (default).
|
||||
// "Crosshair" for shared crosshair.
|
||||
// "Tooltip" for shared crosshair AND shared tooltip.
|
||||
cursorSync: DashboardCursorSync
|
||||
|
||||
// When set to true, the dashboard will redraw panels at an interval matching the pixel width.
|
||||
// This will keep data "moving left" regardless of the query refresh rate. This setting helps
|
||||
// avoid dashboards presenting stale live data.
|
||||
liveNow?: bool
|
||||
|
||||
// When set to true, the dashboard will load all panels in the dashboard when it's loaded.
|
||||
preload: bool
|
||||
|
||||
// Whether a dashboard is editable or not.
|
||||
editable?: bool | *true
|
||||
|
||||
// Links with references to other dashboards or external websites.
|
||||
links: [...DashboardLink]
|
||||
|
||||
// Tags associated with dashboard.
|
||||
tags?: [...string]
|
||||
|
||||
timeSettings: TimeSettingsSpec
|
||||
|
||||
// Configured template variables.
|
||||
variables: [...QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind]
|
||||
|
||||
elements: [ElementReference.name]: PanelKind // |* more element types in the future
|
||||
|
||||
annotations: [...AnnotationQueryKind]
|
||||
|
||||
layout: GridLayoutKind
|
||||
|
||||
// version: will rely on k8s resource versioning, via metadata.resorceVersion
|
||||
|
||||
// revision?: int // for plugins only
|
||||
// gnetId?: string // ??? Wat is this used for?
|
||||
}
|
||||
|
||||
|
||||
AnnotationPanelFilter: {
|
||||
// Should the specified panels be included or excluded
|
||||
exclude?: bool | *false
|
||||
|
||||
// Panel IDs that should be included or excluded
|
||||
ids: [...uint8]
|
||||
}
|
||||
|
||||
// "Off" for no shared crosshair or tooltip (default).
|
||||
// "Crosshair" for shared crosshair.
|
||||
// "Tooltip" for shared crosshair AND shared tooltip.
|
||||
DashboardCursorSync: "Off" | "Crosshair" | "Tooltip"
|
||||
|
||||
// Links with references to other dashboards or external resources
|
||||
DashboardLink: {
|
||||
// Title to display with the link
|
||||
title: string
|
||||
// Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
|
||||
// FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType`
|
||||
type: DashboardLinkType
|
||||
// Icon name to be displayed with the link
|
||||
icon: string
|
||||
// Tooltip to display when the user hovers their mouse over it
|
||||
tooltip: string
|
||||
// Link URL. Only required/valid if the type is link
|
||||
url?: string
|
||||
// List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards
|
||||
tags: [...string]
|
||||
// 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
|
||||
asDropdown: bool | *false
|
||||
// If true, the link will be opened in a new tab
|
||||
targetBlank: bool | *false
|
||||
// If true, includes current template variables values in the link as query params
|
||||
includeVars: bool | *false
|
||||
// If true, includes current time range in the link as query params
|
||||
keepTime: bool | *false
|
||||
}
|
||||
|
||||
DataSourceRef: {
|
||||
// The plugin type-id
|
||||
type?: string
|
||||
|
||||
// Specific datasource instance
|
||||
uid?: string
|
||||
}
|
||||
|
||||
// Transformations allow 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,
|
||||
// use the output of one transformation as the input to another transformation, etc.
|
||||
DataTransformerConfig: {
|
||||
// Unique identifier of transformer
|
||||
id: string
|
||||
// Disabled transformations are skipped
|
||||
disabled?: bool
|
||||
// Optional frame matcher. When missing it will be applied to all results
|
||||
filter?: MatcherConfig
|
||||
// Where to pull DataFrames from as input to transformation
|
||||
topic?: "series" | "annotations" | "alertStates" // replaced with common.DataTopic
|
||||
// Options to be passed to the transformer
|
||||
// Valid options depend on the transformer id
|
||||
options: _
|
||||
}
|
||||
|
||||
// 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.
|
||||
FieldConfigSource: {
|
||||
// Defaults are the options applied to all fields.
|
||||
defaults: FieldConfig
|
||||
// Overrides are the options applied to specific fields overriding the defaults.
|
||||
overrides: [...{
|
||||
matcher: MatcherConfig
|
||||
properties: [...DynamicConfigValue]
|
||||
}]
|
||||
}
|
||||
|
||||
// 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.
|
||||
FieldConfig: {
|
||||
// The display value for this field. This supports template variables blank is auto
|
||||
displayName?: string
|
||||
|
||||
// This can be used by data sources that return and explicit naming structure for values and labels
|
||||
// When this property is configured, this value is used rather than the default naming strategy.
|
||||
displayNameFromDS?: string
|
||||
|
||||
// Human readable field metadata
|
||||
description?: string
|
||||
|
||||
// An explicit path to the field in the datasource. 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 datasource scope, and
|
||||
// may be used to update the results
|
||||
path?: string
|
||||
|
||||
// True if data source can write a value to the path. Auth/authz are supported separately
|
||||
writeable?: bool
|
||||
|
||||
// True if data source field supports ad-hoc filters
|
||||
filterable?: bool
|
||||
|
||||
// Unit a field should use. The unit you select is applied to all fields except time.
|
||||
// You can use the units ID availables 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 unit, you can use the following formats:
|
||||
// `suffix:<suffix>` for custom unit that should go after value.
|
||||
// `prefix:<prefix>` for custom unit that should go before value.
|
||||
// `time:<format>` For custom date time formats type for example `time:YYYY-MM-DD`.
|
||||
// `si:<base scale><unit characters>` 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.
|
||||
// `count:<unit>` for a custom count unit.
|
||||
// `currency:<unit>` for custom a currency unit.
|
||||
unit?: string
|
||||
|
||||
// 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`.
|
||||
decimals?: number
|
||||
|
||||
// The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
|
||||
min?: number
|
||||
// The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
|
||||
max?: number
|
||||
|
||||
// Convert input values into a display string
|
||||
mappings?: [...ValueMapping]
|
||||
|
||||
// Map numeric values to states
|
||||
thresholds?: ThresholdsConfig
|
||||
|
||||
// Panel color configuration
|
||||
color?: FieldColor
|
||||
|
||||
// The behavior when clicking on a result
|
||||
links?: [...]
|
||||
|
||||
// Alternative to empty string
|
||||
noValue?: string
|
||||
|
||||
// custom is specified by the FieldConfig field
|
||||
// in panel plugin schemas.
|
||||
custom?: {...}
|
||||
}
|
||||
|
||||
DynamicConfigValue: {
|
||||
id: string | *""
|
||||
value?: _
|
||||
}
|
||||
|
||||
// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.
|
||||
// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.
|
||||
MatcherConfig: {
|
||||
// The matcher id. This is used to find the matcher implementation from registry.
|
||||
id: string | *""
|
||||
// The matcher options. This is specific to the matcher implementation.
|
||||
options?: _
|
||||
}
|
||||
|
||||
Threshold: {
|
||||
value: number | null
|
||||
color: string
|
||||
}
|
||||
|
||||
ThresholdsMode: "absolute" | "percentage"
|
||||
|
||||
ThresholdsConfig: {
|
||||
mode: ThresholdsMode
|
||||
steps: [...Threshold]
|
||||
}
|
||||
|
||||
ValueMapping: ValueMap | RangeMap | RegexMap | SpecialValueMap
|
||||
|
||||
// Supported value mapping types
|
||||
// `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.
|
||||
// `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.
|
||||
// `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.
|
||||
// `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.
|
||||
MappingType: "value" | "range" | "regex" | "special"
|
||||
|
||||
// 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.
|
||||
ValueMap: {
|
||||
type: MappingType & "value"
|
||||
// Map with <value_to_match>: ValueMappingResult. For example: { "10": { text: "Perfection!", color: "green" } }
|
||||
options: [string]: ValueMappingResult
|
||||
}
|
||||
|
||||
// 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.
|
||||
RangeMap: {
|
||||
type: MappingType & "range"
|
||||
// Range to match against and the result to apply when the value is within the range
|
||||
options: {
|
||||
// Min value of the range. It can be null which means -Infinity
|
||||
from: float64 | null
|
||||
// Max value of the range. It can be null which means +Infinity
|
||||
to: float64 | null
|
||||
// Config to apply when the value is within the range
|
||||
result: ValueMappingResult
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
RegexMap: {
|
||||
type: MappingType & "regex"
|
||||
// Regular expression to match against and the result to apply when the value matches the regex
|
||||
options: {
|
||||
// Regular expression to match against
|
||||
pattern: string
|
||||
// Config to apply when the value matches the regex
|
||||
result: ValueMappingResult
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
SpecialValueMap: {
|
||||
type: MappingType & "special"
|
||||
options: {
|
||||
// Special value to match against
|
||||
match: SpecialValueMatch
|
||||
// Config to apply when the value matches the special value
|
||||
result: ValueMappingResult
|
||||
}
|
||||
}
|
||||
|
||||
// Special value types supported by the `SpecialValueMap`
|
||||
SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty"
|
||||
|
||||
// Result used as replacement with text and color when the value matches
|
||||
ValueMappingResult: {
|
||||
// Text to display when the value matches
|
||||
text?: string
|
||||
// Text to use when the value matches
|
||||
color?: string
|
||||
// Icon to display when the value matches. Only specific visualizations.
|
||||
icon?: string
|
||||
// Position in the mapping array. Only used internally.
|
||||
index?: int32
|
||||
}
|
||||
|
||||
// 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:
|
||||
// `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`: ontinuous 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.
|
||||
FieldColorModeId: "thresholds" | "palette-classic" | "palette-classic-by-name" | "continuous-GrYlRd" | "continuous-RdYlGr" | "continuous-BlYlRd" | "continuous-YlRd" | "continuous-BlPu" | "continuous-YlBl" | "continuous-blues" | "continuous-reds" | "continuous-greens" | "continuous-purples" | "fixed" | "shades"
|
||||
|
||||
// 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.
|
||||
FieldColorSeriesByMode: "min" | "max" | "last"
|
||||
|
||||
// Map a field to a color.
|
||||
FieldColor: {
|
||||
// The main color scheme mode.
|
||||
mode: FieldColorModeId
|
||||
// The fixed color value for fixed or shades color modes.
|
||||
fixedColor?: string
|
||||
// Some visualizations need to know how to assign a series color from by value color schemes.
|
||||
seriesBy?: FieldColorSeriesByMode
|
||||
}
|
||||
|
||||
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
|
||||
DashboardLinkType: "link" | "dashboards"
|
||||
|
||||
// --- Common types ---
|
||||
Kind: {
|
||||
kind: string,
|
||||
spec: _
|
||||
metadata?: _
|
||||
}
|
||||
|
||||
// --- Kinds ---
|
||||
VizConfigSpec: {
|
||||
pluginVersion: string
|
||||
options: [string]: _
|
||||
fieldConfig: FieldConfigSource
|
||||
}
|
||||
|
||||
VizConfigKind: {
|
||||
// The kind of a VizConfigKind is the plugin ID
|
||||
kind: string
|
||||
spec: VizConfigSpec
|
||||
}
|
||||
|
||||
AnnotationQuerySpec: {
|
||||
datasource: DataSourceRef
|
||||
query: DataQueryKind
|
||||
|
||||
// TODO: Should be figured out based on datasource (Grafana ds)
|
||||
// builtIn?: int
|
||||
// Below are currently existing options for annotation queries
|
||||
enable: bool
|
||||
filter: AnnotationPanelFilter
|
||||
hide: bool
|
||||
iconColor: string
|
||||
name: string
|
||||
}
|
||||
|
||||
AnnotationQueryKind: {
|
||||
kind: "AnnotationQuery"
|
||||
spec: AnnotationQuerySpec
|
||||
}
|
||||
|
||||
QueryOptionsSpec: {
|
||||
timeFrom?: string
|
||||
maxDataPoints?: int
|
||||
timeShift?: string
|
||||
queryCachingTTL?: int
|
||||
interval?: string
|
||||
cacheTimeout?: string
|
||||
}
|
||||
|
||||
DataQueryKind: {
|
||||
// The kind of a DataQueryKind is the datasource type
|
||||
kind: string
|
||||
spec: [string]: _
|
||||
}
|
||||
|
||||
PanelQuerySpec: {
|
||||
query: DataQueryKind
|
||||
datasource: DataSourceRef
|
||||
|
||||
refId: string
|
||||
hidden: bool
|
||||
}
|
||||
|
||||
PanelQueryKind: {
|
||||
kind: "PanelQuery"
|
||||
spec: PanelQuerySpec
|
||||
}
|
||||
|
||||
TransformationKind: {
|
||||
// The kind of a TransformationKind is the transformation ID
|
||||
kind: string
|
||||
spec: DataTransformerConfig
|
||||
}
|
||||
|
||||
QueryGroupSpec: {
|
||||
queries: [...PanelQueryKind]
|
||||
transformations: [...TransformationKind]
|
||||
queryOptions: QueryOptionsSpec
|
||||
}
|
||||
|
||||
QueryGroupKind: {
|
||||
kind: "QueryGroup"
|
||||
spec: QueryGroupSpec
|
||||
}
|
||||
|
||||
// Time configuration
|
||||
// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
|
||||
TimeSettingsSpec: {
|
||||
// Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc".
|
||||
timezone?: string | *"browser"
|
||||
// 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".
|
||||
from: string | *"now-6h"
|
||||
// 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".
|
||||
to: string | *"now"
|
||||
// Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d".
|
||||
autoRefresh: string // v1: refresh
|
||||
// Interval options available in the refresh picker dropdown.
|
||||
autoRefreshIntervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] // v1: timepicker.refresh_intervals
|
||||
// Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.
|
||||
quickRanges: [...string] | *["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] // v1: timepicker.time_options , not exposed in the UI
|
||||
// Whether timepicker is visible or not.
|
||||
hideTimepicker: bool // v1: timepicker.hidden
|
||||
// Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday".
|
||||
weekStart: string
|
||||
// The month that the fiscal year starts on. 0 = January, 11 = December
|
||||
fiscalYearStartMonth: int
|
||||
// Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.
|
||||
nowDelay?: string // v1: timepicker.nowDelay
|
||||
}
|
||||
|
||||
GridLayoutItemSpec: {
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
element: ElementReference // reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference
|
||||
}
|
||||
|
||||
GridLayoutItemKind: {
|
||||
kind: "GridLayoutItem"
|
||||
spec: GridLayoutItemSpec
|
||||
}
|
||||
|
||||
GridLayoutSpec: {
|
||||
items: [...GridLayoutItemKind]
|
||||
}
|
||||
|
||||
GridLayoutKind: {
|
||||
kind: "GridLayout"
|
||||
spec: GridLayoutSpec
|
||||
}
|
||||
|
||||
PanelSpec: {
|
||||
uid: string
|
||||
title: string
|
||||
description: string
|
||||
links: [...DashboardLink]
|
||||
data: QueryGroupKind
|
||||
vizConfig: VizConfigKind
|
||||
}
|
||||
|
||||
PanelKind: {
|
||||
kind: "Panel"
|
||||
spec: PanelSpec
|
||||
}
|
||||
|
||||
ElementReference: {
|
||||
kind: "ElementReference"
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Start FIXME: variables - in CUE PR - this are things that should be added into the cue schema
|
||||
// TODO: properties such as `hide`, `skipUrlSync`, `multi` are type boolean, and in the old schema they are conditional,
|
||||
// should we make them conditional in the new schema as well? or should we make them required but default to false?
|
||||
|
||||
// Variable types
|
||||
VariableValue: VariableValueSingle | [...VariableValueSingle]
|
||||
|
||||
VariableValueSingle: string | bool | number | CustomVariableValue
|
||||
|
||||
// Custom formatter variable
|
||||
CustomFormatterVariable: {
|
||||
name: string
|
||||
type: VariableType
|
||||
multi: bool
|
||||
includeAll: bool
|
||||
}
|
||||
|
||||
// Custom variable value
|
||||
CustomVariableValue: {
|
||||
// The format name or function used in the expression
|
||||
formatter: *null | string | VariableCustomFormatterFn
|
||||
}
|
||||
|
||||
// Custom formatter function
|
||||
VariableCustomFormatterFn: {
|
||||
value: _
|
||||
legacyVariableModel: {
|
||||
name: string
|
||||
type: VariableType
|
||||
multi: bool
|
||||
includeAll: bool
|
||||
}
|
||||
legacyDefaultFormatter?: VariableCustomFormatterFn
|
||||
}
|
||||
|
||||
// Dashboard variable type
|
||||
// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
|
||||
// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).
|
||||
// `constant`: Define a hidden constant.
|
||||
// `datasource`: Quickly change the data source for an entire dashboard.
|
||||
// `interval`: Interval variables represent time spans.
|
||||
// `textbox`: Display a free text input field with an optional default value.
|
||||
// `custom`: Define the variable options manually using a comma-separated list.
|
||||
// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
|
||||
VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" |
|
||||
"system" | "snapshot"
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `disabled`: No sorting
|
||||
// `alphabeticalAsc`: Alphabetical ASC
|
||||
// `alphabeticalDesc`: Alphabetical DESC
|
||||
// `numericalAsc`: Numerical ASC
|
||||
// `numericalDesc`: Numerical DESC
|
||||
// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC
|
||||
// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC
|
||||
// `naturalAsc`: Natural ASC
|
||||
// `naturalDesc`: Natural DESC
|
||||
// VariableSort enum with default value
|
||||
VariableSort: "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"
|
||||
|
||||
// Options to config when to refresh a variable
|
||||
// `never`: Never refresh the variable
|
||||
// `onDashboardLoad`: Queries the data source every time the dashboard loads.
|
||||
// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes.
|
||||
VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
|
||||
|
||||
|
||||
// FIXME: should we introduce this? --- Variable value option
|
||||
VariableValueOption: {
|
||||
label: string
|
||||
value: VariableValueSingle
|
||||
group?: string
|
||||
}
|
||||
|
||||
// Variable option specification
|
||||
VariableOption: {
|
||||
// Whether the option is selected or not
|
||||
selected?: bool
|
||||
// Text to be displayed for the option
|
||||
text: string | [...string]
|
||||
// Value of the option
|
||||
value: string | [...string]
|
||||
}
|
||||
|
||||
// Query variable specification
|
||||
QueryVariableSpec: {
|
||||
name: string | *""
|
||||
current: VariableOption | *{
|
||||
text: ""
|
||||
value: ""
|
||||
}
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
refresh: VariableRefresh
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
datasource: DataSourceRef | *{}
|
||||
query: string | DataQueryKind | *""
|
||||
regex: string | *""
|
||||
sort: VariableSort
|
||||
definition?: string
|
||||
options: [...VariableOption] | *[]
|
||||
multi: bool | *false
|
||||
includeAll: bool | *false
|
||||
allValue?: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
// Query variable kind
|
||||
QueryVariableKind: {
|
||||
kind: "QueryVariable"
|
||||
spec: QueryVariableSpec
|
||||
}
|
||||
|
||||
// Text variable specification
|
||||
TextVariableSpec: {
|
||||
name: string | *""
|
||||
current: VariableOption | *{
|
||||
text: ""
|
||||
value: ""
|
||||
}
|
||||
query: string | *""
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Text variable kind
|
||||
TextVariableKind: {
|
||||
kind: "TextVariable"
|
||||
spec: TextVariableSpec
|
||||
}
|
||||
|
||||
// Constant variable specification
|
||||
ConstantVariableSpec: {
|
||||
name: string | *""
|
||||
query: string | *""
|
||||
current: VariableOption | *{
|
||||
text: ""
|
||||
value: ""
|
||||
}
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Constant variable kind
|
||||
ConstantVariableKind: {
|
||||
kind: "ConstantVariable"
|
||||
spec: ConstantVariableSpec
|
||||
}
|
||||
|
||||
// Datasource variable specification
|
||||
DatasourceVariableSpec: {
|
||||
name: string | *""
|
||||
pluginId: string | *""
|
||||
refresh: VariableRefresh
|
||||
regex: string | *""
|
||||
current: VariableOption | *{
|
||||
text: ""
|
||||
value: ""
|
||||
}
|
||||
defaultOptionEnabled: bool | *false
|
||||
options: [...VariableOption] | *[]
|
||||
multi: bool | *false
|
||||
includeAll: bool | *false
|
||||
allValue?: string
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Datasource variable kind
|
||||
DatasourceVariableKind: {
|
||||
kind: "DatasourceVariable"
|
||||
spec: DatasourceVariableSpec
|
||||
}
|
||||
|
||||
// Interval variable specification
|
||||
IntervalVariableSpec: {
|
||||
name: string | *""
|
||||
query: string | *""
|
||||
current: VariableOption | *{
|
||||
text: ""
|
||||
value: ""
|
||||
}
|
||||
options: [...VariableOption] | *[]
|
||||
auto: bool | *false
|
||||
auto_min: string | *""
|
||||
auto_count: int | *0
|
||||
refresh: VariableRefresh
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Interval variable kind
|
||||
IntervalVariableKind: {
|
||||
kind: "IntervalVariable"
|
||||
spec: IntervalVariableSpec
|
||||
}
|
||||
|
||||
// Custom variable specification
|
||||
CustomVariableSpec: {
|
||||
name: string | *""
|
||||
query: string | *""
|
||||
current: VariableOption
|
||||
options: [...VariableOption] | *[]
|
||||
multi: bool | *false
|
||||
includeAll: bool | *false
|
||||
allValue?: string
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Custom variable kind
|
||||
CustomVariableKind: {
|
||||
kind: "CustomVariable"
|
||||
spec: CustomVariableSpec
|
||||
}
|
||||
|
||||
// GroupBy variable specification
|
||||
GroupByVariableSpec: {
|
||||
name: string | *""
|
||||
datasource: DataSourceRef | *{}
|
||||
current: VariableOption | *{
|
||||
text: ""
|
||||
value: ""
|
||||
}
|
||||
options: [...VariableOption] | *[]
|
||||
multi: bool | *false
|
||||
includeAll: bool | *false
|
||||
allValue?: string
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Group variable kind
|
||||
GroupByVariableKind: {
|
||||
kind: "GroupByVariable"
|
||||
spec: GroupByVariableSpec
|
||||
}
|
||||
|
||||
// Adhoc variable specification
|
||||
AdhocVariableSpec: {
|
||||
name: string | *""
|
||||
datasource: DataSourceRef | *{}
|
||||
baseFilters: [...AdHocFilterWithLabels] | *[]
|
||||
filters: [...AdHocFilterWithLabels] | *[]
|
||||
defaultKeys: [...MetricFindValue] | *[]
|
||||
label?: string
|
||||
hide: VariableHide
|
||||
skipUrlSync: bool | *false
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Define the MetricFindValue type
|
||||
MetricFindValue: {
|
||||
text: string
|
||||
value?: string | number
|
||||
group?: string
|
||||
expandable?: bool
|
||||
}
|
||||
|
||||
// Define the AdHocFilterWithLabels type
|
||||
AdHocFilterWithLabels: {
|
||||
key: string,
|
||||
operator: string,
|
||||
value: string,
|
||||
values?: [...string],
|
||||
keyLabel?: string,
|
||||
valueLabels?: [...string],
|
||||
forceEdit?: bool,
|
||||
// @deprecated
|
||||
condition?: string,
|
||||
}
|
||||
|
||||
// Adhoc variable kind
|
||||
AdhocVariableKind: {
|
||||
kind: "AdhocVariable"
|
||||
spec: AdhocVariableSpec
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
import { DashboardCursorSync, DashboardV2Spec } from './dashboard.gen';
|
||||
|
||||
export const handyTestingSchema: DashboardV2Spec = {
|
||||
id: 1,
|
||||
title: 'Default Dashboard',
|
||||
description: 'This is a default dashboard',
|
||||
cursorSync: DashboardCursorSync.Off,
|
||||
liveNow: false,
|
||||
preload: false,
|
||||
editable: true,
|
||||
links: [],
|
||||
tags: [],
|
||||
timeSettings: {
|
||||
timezone: 'browser',
|
||||
from: 'now-6h',
|
||||
to: 'now',
|
||||
autoRefresh: '10s',
|
||||
autoRefreshIntervals: ['10s', '1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'],
|
||||
quickRanges: ['now/d', 'now/w', 'now/M', 'now/y'],
|
||||
hideTimepicker: false,
|
||||
weekStart: 'sunday',
|
||||
fiscalYearStartMonth: 1,
|
||||
},
|
||||
|
||||
elements: {
|
||||
timeSeriesTest: {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
title: 'Time Series Test',
|
||||
description: 'This is a test panel',
|
||||
uid: 'timeSeriesTest',
|
||||
links: [],
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [
|
||||
{
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
query: {
|
||||
kind: 'prometheus',
|
||||
spec: {
|
||||
query: 'up',
|
||||
},
|
||||
},
|
||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||
hidden: false,
|
||||
refId: 'A',
|
||||
},
|
||||
},
|
||||
],
|
||||
transformations: [
|
||||
{
|
||||
kind: 'limit',
|
||||
spec: {
|
||||
id: 'limit', // id is competing w/ kind
|
||||
options: {
|
||||
limit: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
queryOptions: {
|
||||
maxDataPoints: 100,
|
||||
cacheTimeout: '1m',
|
||||
},
|
||||
},
|
||||
},
|
||||
vizConfig: {
|
||||
kind: 'timeseries',
|
||||
spec: {
|
||||
pluginVersion: '11.0.0',
|
||||
options: {},
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
kind: 'GridLayout',
|
||||
spec: {
|
||||
items: [
|
||||
{
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
element: { kind: 'ElementReference', name: 'timeSeriesTest' },
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 6,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
variables: [],
|
||||
annotations: [
|
||||
{
|
||||
kind: 'AnnotationQuery',
|
||||
spec: {
|
||||
datasource: { type: 'datasource', uid: 'grafana' },
|
||||
query: {
|
||||
kind: 'grafana',
|
||||
spec: {
|
||||
queryType: 'timeRegions',
|
||||
matchAny: false,
|
||||
timeRegion: {
|
||||
from: '12:27',
|
||||
fromDayOfWeek: 2,
|
||||
timezone: 'browser',
|
||||
to: '11:30',
|
||||
toDayOfWeek: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
enable: true,
|
||||
filter: {
|
||||
ids: [],
|
||||
},
|
||||
hide: false,
|
||||
iconColor: 'blue',
|
||||
name: 'Grafana annotations',
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'AnnotationQuery',
|
||||
spec: {
|
||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||
query: {
|
||||
kind: 'prometheus',
|
||||
spec: {
|
||||
query: 'up',
|
||||
},
|
||||
},
|
||||
enable: true,
|
||||
filter: {
|
||||
ids: [],
|
||||
},
|
||||
hide: false,
|
||||
iconColor: 'red',
|
||||
name: 'Prometheus annotations',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@ -25,7 +25,7 @@ import {
|
||||
} from '@grafana/scenes';
|
||||
import { DataSourceRef, VariableRefresh } from '@grafana/schema';
|
||||
|
||||
import { sceneVariablesSetToVariables } from './sceneVariablesSetToVariables';
|
||||
import { sceneVariablesSetToSchemaV2Variables, sceneVariablesSetToVariables } from './sceneVariablesSetToVariables';
|
||||
|
||||
const runRequestMock = jest.fn().mockReturnValue(
|
||||
of<PanelData>({
|
||||
@ -713,4 +713,555 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
expect(() => sceneVariablesSetToVariables(set)).toThrow('Unsupported variable type');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sceneVariablesSetToSchemaV2Variables', () => {
|
||||
it('should handle QueryVariable', () => {
|
||||
const variable = new QueryVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
value: ['selected-value'],
|
||||
text: ['selected-value-text'],
|
||||
datasource: { uid: 'fake-std', type: 'fake-std' },
|
||||
query: 'query',
|
||||
includeAll: true,
|
||||
allValue: 'test-all',
|
||||
isMulti: true,
|
||||
});
|
||||
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "QueryVariable",
|
||||
"spec": {
|
||||
"allValue": "test-all",
|
||||
"current": {
|
||||
"text": [
|
||||
"selected-value-text",
|
||||
],
|
||||
"value": [
|
||||
"selected-value",
|
||||
],
|
||||
},
|
||||
"datasource": {
|
||||
"type": "fake-std",
|
||||
"uid": "fake-std",
|
||||
},
|
||||
"definition": undefined,
|
||||
"description": "test-desc",
|
||||
"hide": "dontHide",
|
||||
"includeAll": true,
|
||||
"label": "test-label",
|
||||
"multi": true,
|
||||
"name": "test",
|
||||
"options": [],
|
||||
"query": "query",
|
||||
"refresh": "onDashboardLoad",
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": "disabled",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle CustomVariable', () => {
|
||||
const variable = new CustomVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
value: ['test', 'test2'],
|
||||
text: ['test', 'test2'],
|
||||
query: 'test,test1,test2',
|
||||
options: [
|
||||
{ label: 'test', value: 'test' },
|
||||
{ label: 'test1', value: 'test1' },
|
||||
{ label: 'test2', value: 'test2' },
|
||||
],
|
||||
includeAll: true,
|
||||
allValue: 'test-all',
|
||||
isMulti: true,
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "CustomVariable",
|
||||
"spec": {
|
||||
"allValue": "test-all",
|
||||
"current": {
|
||||
"text": [
|
||||
"test",
|
||||
"test2",
|
||||
],
|
||||
"value": [
|
||||
"test",
|
||||
"test2",
|
||||
],
|
||||
},
|
||||
"description": "test-desc",
|
||||
"hide": "dontHide",
|
||||
"includeAll": true,
|
||||
"label": "test-label",
|
||||
"multi": true,
|
||||
"name": "test",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "test",
|
||||
"value": "test",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "test1",
|
||||
"value": "test1",
|
||||
},
|
||||
{
|
||||
"selected": true,
|
||||
"text": "test2",
|
||||
"value": "test2",
|
||||
},
|
||||
],
|
||||
"query": "test,test1,test2",
|
||||
"skipUrlSync": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle DatasourceVariable', () => {
|
||||
const variable = new DataSourceVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
value: ['selected-ds-1', 'selected-ds-2'],
|
||||
text: ['selected-ds-1-text', 'selected-ds-2-text'],
|
||||
pluginId: 'fake-std',
|
||||
includeAll: true,
|
||||
allValue: 'test-all',
|
||||
isMulti: true,
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "DatasourceVariable",
|
||||
"spec": {
|
||||
"allValue": "test-all",
|
||||
"current": {
|
||||
"text": [
|
||||
"selected-ds-1-text",
|
||||
"selected-ds-2-text",
|
||||
],
|
||||
"value": [
|
||||
"selected-ds-1",
|
||||
"selected-ds-2",
|
||||
],
|
||||
},
|
||||
"defaultOptionEnabled": false,
|
||||
"description": "test-desc",
|
||||
"hide": "dontHide",
|
||||
"includeAll": true,
|
||||
"label": "test-label",
|
||||
"multi": true,
|
||||
"name": "test",
|
||||
"options": [],
|
||||
"pluginId": "fake-std",
|
||||
"refresh": "onDashboardLoad",
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle ConstantVariable', () => {
|
||||
const variable = new ConstantVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
value: 'constant value',
|
||||
skipUrlSync: true,
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "ConstantVariable",
|
||||
"spec": {
|
||||
"current": {
|
||||
"text": undefined,
|
||||
"value": "constant value",
|
||||
},
|
||||
"description": "test-desc",
|
||||
"hide": "dontHide",
|
||||
"label": "test-label",
|
||||
"name": "test",
|
||||
"query": "constant value",
|
||||
"skipUrlSync": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle TextBoxVariable', () => {
|
||||
const variable = new TextBoxVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
value: 'text value',
|
||||
skipUrlSync: true,
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "TextVariable",
|
||||
"spec": {
|
||||
"current": {
|
||||
"text": "text value",
|
||||
"value": "text value",
|
||||
},
|
||||
"description": "test-desc",
|
||||
"hide": "dontHide",
|
||||
"label": "test-label",
|
||||
"name": "test",
|
||||
"query": "text value",
|
||||
"skipUrlSync": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle IntervalVariable', () => {
|
||||
const variable = new IntervalVariable({
|
||||
intervals: ['1m', '2m', '3m', '1h', '1d'],
|
||||
value: '1m',
|
||||
refresh: VariableRefresh.onDashboardLoad,
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "IntervalVariable",
|
||||
"spec": {
|
||||
"auto": false,
|
||||
"auto_count": 30,
|
||||
"auto_min": "10s",
|
||||
"current": {
|
||||
"text": undefined,
|
||||
"value": "1m",
|
||||
},
|
||||
"description": undefined,
|
||||
"hide": "dontHide",
|
||||
"label": undefined,
|
||||
"name": "",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "1m",
|
||||
"value": "1m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "2m",
|
||||
"value": "2m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "3m",
|
||||
"value": "3m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1h",
|
||||
"value": "1h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1d",
|
||||
"value": "1d",
|
||||
},
|
||||
],
|
||||
"query": "1m,2m,3m,1h,1d",
|
||||
"refresh": "onTimeRangeChanged",
|
||||
"skipUrlSync": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle AdHocFiltersVariable', () => {
|
||||
const variable = new AdHocFiltersVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
datasource: { uid: 'fake-std', type: 'fake-std' },
|
||||
filters: [
|
||||
{
|
||||
key: 'filterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
baseFilters: [
|
||||
{
|
||||
key: 'baseFilterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "AdhocVariable",
|
||||
"spec": {
|
||||
"baseFilters": [
|
||||
{
|
||||
"key": "baseFilterTest",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"datasource": {
|
||||
"type": "fake-std",
|
||||
"uid": "fake-std",
|
||||
},
|
||||
"defaultKeys": [],
|
||||
"description": "test-desc",
|
||||
"filters": [
|
||||
{
|
||||
"key": "filterTest",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"hide": "dontHide",
|
||||
"label": "test-label",
|
||||
"name": "test",
|
||||
"skipUrlSync": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle AdHocFiltersVariable with defaultKeys', () => {
|
||||
const variable = new AdHocFiltersVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
datasource: { uid: 'fake-std', type: 'fake-std' },
|
||||
defaultKeys: [
|
||||
{
|
||||
text: 'some',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
text: 'static',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
text: 'keys',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'filterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
baseFilters: [
|
||||
{
|
||||
key: 'baseFilterTest',
|
||||
operator: '=',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "AdhocVariable",
|
||||
"spec": {
|
||||
"baseFilters": [
|
||||
{
|
||||
"key": "baseFilterTest",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"datasource": {
|
||||
"type": "fake-std",
|
||||
"uid": "fake-std",
|
||||
},
|
||||
"defaultKeys": [
|
||||
{
|
||||
"text": "some",
|
||||
"value": "1",
|
||||
},
|
||||
{
|
||||
"text": "static",
|
||||
"value": "2",
|
||||
},
|
||||
{
|
||||
"text": "keys",
|
||||
"value": "3",
|
||||
},
|
||||
],
|
||||
"description": "test-desc",
|
||||
"filters": [
|
||||
{
|
||||
"key": "filterTest",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"hide": "dontHide",
|
||||
"label": "test-label",
|
||||
"name": "test",
|
||||
"skipUrlSync": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('when the groupByVariable feature toggle is enabled', () => {
|
||||
beforeAll(() => {
|
||||
config.featureToggles.groupByVariable = true;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
config.featureToggles.groupByVariable = false;
|
||||
});
|
||||
|
||||
it('should handle GroupByVariable', () => {
|
||||
const variable = new GroupByVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
datasource: { uid: 'fake-std', type: 'fake-std' },
|
||||
defaultOptions: [
|
||||
{
|
||||
text: 'Foo',
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
text: 'Bar',
|
||||
value: 'bar',
|
||||
},
|
||||
],
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
const result = sceneVariablesSetToSchemaV2Variables(set);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"kind": "GroupByVariable",
|
||||
"spec": {
|
||||
"current": {
|
||||
"text": [],
|
||||
"value": [],
|
||||
},
|
||||
"datasource": {
|
||||
"type": "fake-std",
|
||||
"uid": "fake-std",
|
||||
},
|
||||
"description": "test-desc",
|
||||
"hide": "dontHide",
|
||||
"includeAll": false,
|
||||
"label": "test-label",
|
||||
"multi": true,
|
||||
"name": "test",
|
||||
"options": [
|
||||
{
|
||||
"text": "Foo",
|
||||
"value": "foo",
|
||||
},
|
||||
{
|
||||
"text": "Bar",
|
||||
"value": "bar",
|
||||
},
|
||||
],
|
||||
"skipUrlSync": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the groupByVariable feature toggle is disabled', () => {
|
||||
it('should not handle GroupByVariable and throw an error', () => {
|
||||
const variable = new GroupByVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
description: 'test-desc',
|
||||
datasource: { uid: 'fake-std', type: 'fake-std' },
|
||||
defaultOptions: [
|
||||
{
|
||||
text: 'Foo',
|
||||
value: 'foo',
|
||||
},
|
||||
{
|
||||
text: 'Bar',
|
||||
value: 'bar',
|
||||
},
|
||||
],
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
});
|
||||
|
||||
expect(() => sceneVariablesSetToSchemaV2Variables(set)).toThrow('Unsupported variable type');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,34 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
import { MultiValueVariable, SceneVariables, sceneUtils } from '@grafana/scenes';
|
||||
import { VariableHide, VariableModel, VariableOption, VariableRefresh, VariableSort } from '@grafana/schema';
|
||||
import {
|
||||
VariableModel,
|
||||
VariableRefresh as OldVariableRefresh,
|
||||
VariableHide as OldVariableHide,
|
||||
VariableSort as OldVariableSort,
|
||||
} from '@grafana/schema';
|
||||
import {
|
||||
AdhocVariableKind,
|
||||
ConstantVariableKind,
|
||||
CustomVariableKind,
|
||||
DataQueryKind,
|
||||
DatasourceVariableKind,
|
||||
IntervalVariableKind,
|
||||
QueryVariableKind,
|
||||
TextVariableKind,
|
||||
GroupByVariableKind,
|
||||
defaultVariableHide,
|
||||
VariableOption,
|
||||
VariableRefresh,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
|
||||
|
||||
import { getIntervalsQueryFromNewIntervalModel } from '../utils/utils';
|
||||
|
||||
import { getDataQueryKind, getDataQuerySpec } from './transformSceneToSaveModelSchemaV2';
|
||||
import {
|
||||
transformVariableRefreshToEnum,
|
||||
transformVariableHideToEnum,
|
||||
transformSortVariableToEnum,
|
||||
} from './transformToV2TypesUtils';
|
||||
/**
|
||||
* Converts a SceneVariables object into an array of VariableModel objects.
|
||||
* @param set - The SceneVariables object containing the variables to convert.
|
||||
@ -21,14 +46,14 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
label: variable.state.label,
|
||||
description: variable.state.description ?? undefined,
|
||||
skipUrlSync: Boolean(variable.state.skipUrlSync),
|
||||
hide: variable.state.hide || VariableHide.dontHide,
|
||||
hide: variable.state.hide || OldVariableHide.dontHide,
|
||||
type: variable.state.type,
|
||||
};
|
||||
if (sceneUtils.isQueryVariable(variable)) {
|
||||
let options: VariableOption[] = [];
|
||||
// Not sure if we actually have to still support this option given
|
||||
// that it's not exposed in the UI
|
||||
if (variable.state.refresh === VariableRefresh.never || keepQueryOptions) {
|
||||
if (transformVariableRefreshToEnum(variable.state.refresh) === VariableRefresh.Never || keepQueryOptions) {
|
||||
options = variableValueOptionsToVariableOptions(variable.state);
|
||||
}
|
||||
variables.push({
|
||||
@ -77,7 +102,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
},
|
||||
options: [],
|
||||
regex: variable.state.regex,
|
||||
refresh: VariableRefresh.onDashboardLoad,
|
||||
refresh: OldVariableRefresh.onDashboardLoad,
|
||||
query: variable.state.pluginId,
|
||||
multi: variable.state.isMulti,
|
||||
allValue: variable.state.allValue,
|
||||
@ -94,7 +119,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
},
|
||||
// @ts-expect-error
|
||||
query: variable.state.value,
|
||||
hide: VariableHide.hideVariable,
|
||||
hide: OldVariableHide.hideVariable,
|
||||
});
|
||||
} else if (sceneUtils.isIntervalVariable(variable)) {
|
||||
const intervals = getIntervalsQueryFromNewIntervalModel(variable.state.intervals);
|
||||
@ -162,7 +187,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
|
||||
// Remove some defaults
|
||||
for (const variable of variables) {
|
||||
if (variable.hide === VariableHide.dontHide) {
|
||||
if (variable.hide === OldVariableHide.dontHide) {
|
||||
delete variable.hide;
|
||||
}
|
||||
|
||||
@ -178,7 +203,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
delete variable.multi;
|
||||
}
|
||||
|
||||
if (variable.sort === VariableSort.disabled) {
|
||||
if (variable.sort === OldVariableSort.disabled) {
|
||||
delete variable.sort;
|
||||
}
|
||||
}
|
||||
@ -193,3 +218,201 @@ function variableValueOptionsToVariableOptions(varState: MultiValueVariable['sta
|
||||
selected: Array.isArray(varState.value) ? varState.value.includes(o.value) : varState.value === o.value,
|
||||
}));
|
||||
}
|
||||
|
||||
export function sceneVariablesSetToSchemaV2Variables(
|
||||
set: SceneVariables,
|
||||
keepQueryOptions?: boolean
|
||||
): Array<
|
||||
| QueryVariableKind
|
||||
| TextVariableKind
|
||||
| IntervalVariableKind
|
||||
| DatasourceVariableKind
|
||||
| CustomVariableKind
|
||||
| ConstantVariableKind
|
||||
| GroupByVariableKind
|
||||
| AdhocVariableKind
|
||||
> {
|
||||
let variables: Array<
|
||||
| QueryVariableKind
|
||||
| TextVariableKind
|
||||
| IntervalVariableKind
|
||||
| DatasourceVariableKind
|
||||
| CustomVariableKind
|
||||
| ConstantVariableKind
|
||||
| GroupByVariableKind
|
||||
| AdhocVariableKind
|
||||
> = [];
|
||||
|
||||
for (const variable of set.state.variables) {
|
||||
const commonProperties = {
|
||||
name: variable.state.name,
|
||||
label: variable.state.label,
|
||||
description: variable.state.description ?? undefined,
|
||||
skipUrlSync: Boolean(variable.state.skipUrlSync),
|
||||
hide: transformVariableHideToEnum(variable.state.hide) || defaultVariableHide(),
|
||||
};
|
||||
|
||||
// current: VariableOption;
|
||||
const currentVariableOption: VariableOption = {
|
||||
// @ts-expect-error
|
||||
value: variable.state.value,
|
||||
// @ts-expect-error
|
||||
text: variable.state.text,
|
||||
};
|
||||
|
||||
let options: VariableOption[] = [];
|
||||
if (sceneUtils.isQueryVariable(variable)) {
|
||||
// Not sure if we actually have to still support this option given
|
||||
// that it's not exposed in the UI
|
||||
if (transformVariableRefreshToEnum(variable.state.refresh) === VariableRefresh.Never || keepQueryOptions) {
|
||||
options = variableValueOptionsToVariableOptions(variable.state);
|
||||
}
|
||||
//query: DataQueryKind | string;
|
||||
const query = variable.state.query;
|
||||
let dataQuery: DataQueryKind | string;
|
||||
if (typeof query !== 'string') {
|
||||
dataQuery = {
|
||||
kind: getDataQueryKind(query),
|
||||
spec: getDataQuerySpec(query),
|
||||
};
|
||||
} else {
|
||||
dataQuery = query;
|
||||
}
|
||||
const queryVariable: QueryVariableKind = {
|
||||
kind: 'QueryVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
current: currentVariableOption,
|
||||
options,
|
||||
query: dataQuery,
|
||||
definition: variable.state.definition,
|
||||
datasource: variable.state.datasource || {},
|
||||
sort: transformSortVariableToEnum(variable.state.sort),
|
||||
refresh: transformVariableRefreshToEnum(variable.state.refresh),
|
||||
regex: variable.state.regex,
|
||||
allValue: variable.state.allValue,
|
||||
includeAll: variable.state.includeAll || false,
|
||||
multi: variable.state.isMulti || false,
|
||||
skipUrlSync: variable.state.skipUrlSync || false,
|
||||
},
|
||||
};
|
||||
variables.push(queryVariable);
|
||||
} else if (sceneUtils.isCustomVariable(variable)) {
|
||||
options = variableValueOptionsToVariableOptions(variable.state);
|
||||
const customVariable: CustomVariableKind = {
|
||||
kind: 'CustomVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
current: currentVariableOption,
|
||||
options,
|
||||
query: variable.state.query,
|
||||
multi: variable.state.isMulti || false,
|
||||
allValue: variable.state.allValue,
|
||||
includeAll: variable.state.includeAll ?? false,
|
||||
},
|
||||
};
|
||||
variables.push(customVariable);
|
||||
} else if (sceneUtils.isDataSourceVariable(variable)) {
|
||||
const datasourceVariable: DatasourceVariableKind = {
|
||||
kind: 'DatasourceVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
current: currentVariableOption,
|
||||
options: [],
|
||||
regex: variable.state.regex,
|
||||
refresh: VariableRefresh.OnDashboardLoad,
|
||||
pluginId: variable.state.pluginId,
|
||||
defaultOptionEnabled: !!variable.state.defaultOptionEnabled,
|
||||
multi: variable.state.isMulti || false,
|
||||
allValue: variable.state.allValue,
|
||||
includeAll: variable.state.includeAll || false,
|
||||
},
|
||||
};
|
||||
variables.push(datasourceVariable);
|
||||
} else if (sceneUtils.isConstantVariable(variable)) {
|
||||
const constantVariable: ConstantVariableKind = {
|
||||
kind: 'ConstantVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
current: currentVariableOption,
|
||||
// @ts-expect-error
|
||||
query: variable.state.value,
|
||||
},
|
||||
};
|
||||
variables.push(constantVariable);
|
||||
} else if (sceneUtils.isIntervalVariable(variable)) {
|
||||
const intervals = getIntervalsQueryFromNewIntervalModel(variable.state.intervals);
|
||||
const intervalVariable: IntervalVariableKind = {
|
||||
kind: 'IntervalVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
current: currentVariableOption,
|
||||
query: intervals,
|
||||
refresh: VariableRefresh.OnTimeRangeChanged,
|
||||
options: variable.state.intervals.map((interval) => ({
|
||||
value: interval,
|
||||
text: interval,
|
||||
selected: interval === variable.state.value,
|
||||
})),
|
||||
auto: variable.state.autoEnabled,
|
||||
auto_min: variable.state.autoMinInterval,
|
||||
auto_count: variable.state.autoStepCount,
|
||||
},
|
||||
};
|
||||
variables.push(intervalVariable);
|
||||
} else if (sceneUtils.isTextBoxVariable(variable)) {
|
||||
const current = {
|
||||
text: variable.state.value,
|
||||
value: variable.state.value,
|
||||
};
|
||||
|
||||
const textBoxVariable: TextVariableKind = {
|
||||
kind: 'TextVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
current,
|
||||
query: variable.state.value,
|
||||
},
|
||||
};
|
||||
|
||||
variables.push(textBoxVariable);
|
||||
} else if (sceneUtils.isGroupByVariable(variable) && config.featureToggles.groupByVariable) {
|
||||
options = variableValueOptionsToVariableOptions(variable.state);
|
||||
|
||||
const groupVariable: GroupByVariableKind = {
|
||||
kind: 'GroupByVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
datasource: variable.state.datasource || {}, // FIXME what is the default value?,
|
||||
// Only persist the statically defined options
|
||||
options:
|
||||
variable.state.defaultOptions?.map((option) => ({
|
||||
text: option.text,
|
||||
value: String(option.value),
|
||||
})) || [],
|
||||
current: currentVariableOption,
|
||||
multi: variable.state.isMulti || false,
|
||||
includeAll: variable.state.includeAll || false,
|
||||
},
|
||||
};
|
||||
variables.push(groupVariable);
|
||||
} else if (sceneUtils.isAdHocVariable(variable)) {
|
||||
const adhocVariable: AdhocVariableKind = {
|
||||
kind: 'AdhocVariable',
|
||||
spec: {
|
||||
...commonProperties,
|
||||
name: variable.state.name,
|
||||
datasource: variable.state.datasource || {}, //FIXME what is the default value?
|
||||
baseFilters: variable.state.baseFilters || [],
|
||||
filters: variable.state.filters,
|
||||
defaultKeys: variable.state.defaultKeys || [], //FIXME what is the default value?
|
||||
},
|
||||
};
|
||||
variables.push(adhocVariable);
|
||||
} else {
|
||||
throw new Error('Unsupported variable type');
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
@ -0,0 +1,470 @@
|
||||
import { behaviors, SceneDataQuery, SceneDataTransformer, SceneVariableSet, VizPanel } from '@grafana/scenes';
|
||||
|
||||
import {
|
||||
DashboardV2Spec,
|
||||
defaultDashboardV2Spec,
|
||||
defaultFieldConfigSource,
|
||||
PanelKind,
|
||||
PanelQueryKind,
|
||||
TransformationKind,
|
||||
FieldConfigSource,
|
||||
DashboardLink,
|
||||
DashboardCursorSync,
|
||||
DataTransformerConfig,
|
||||
PanelQuerySpec,
|
||||
DataQueryKind,
|
||||
defaultDataSourceRef,
|
||||
GridLayoutItemKind,
|
||||
QueryOptionsSpec,
|
||||
QueryVariableKind,
|
||||
TextVariableKind,
|
||||
IntervalVariableKind,
|
||||
DatasourceVariableKind,
|
||||
CustomVariableKind,
|
||||
ConstantVariableKind,
|
||||
GroupByVariableKind,
|
||||
AdhocVariableKind,
|
||||
} from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen';
|
||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { getQueryRunnerFor } from '../utils/utils';
|
||||
|
||||
import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables';
|
||||
import { transformDashboardLinksToEnums, transformCursorSynctoEnum } from './transformToV2TypesUtils';
|
||||
|
||||
// FIXME: This is temporary to avoid creating partial types for all the new schema, it has some performance implications, but it's fine for now
|
||||
type DeepPartial<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnapshot = false): Partial<DashboardV2Spec> {
|
||||
const oldDash = scene.state;
|
||||
const timeRange = oldDash.$timeRange!.state;
|
||||
|
||||
const controlsState = oldDash.controls?.state;
|
||||
const refreshPicker = controlsState?.refreshPicker;
|
||||
|
||||
const dashboardSchemaV2: DeepPartial<DashboardV2Spec> = {
|
||||
//dashboard settings
|
||||
title: oldDash.title,
|
||||
description: oldDash.description ?? '',
|
||||
cursorSync: getCursorSync(oldDash),
|
||||
liveNow: getLiveNow(oldDash),
|
||||
preload: oldDash.preload,
|
||||
editable: oldDash.editable,
|
||||
links: transformDashboardLinksToEnums(oldDash.links),
|
||||
tags: oldDash.tags,
|
||||
// EOF dashboard settings
|
||||
|
||||
// time settings
|
||||
timeSettings: {
|
||||
timezone: timeRange.timeZone,
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
autoRefresh: refreshPicker?.state.refresh || '',
|
||||
autoRefreshIntervals: refreshPicker?.state.intervals,
|
||||
quickRanges: [], //FIXME is coming timepicker.time_options,
|
||||
hideTimepicker: controlsState?.hideTimeControls ?? false,
|
||||
weekStart: timeRange.weekStart,
|
||||
fiscalYearStartMonth: timeRange.fiscalYearStartMonth,
|
||||
nowDelay: timeRange.UNSAFE_nowDelay,
|
||||
},
|
||||
// EOF time settings
|
||||
|
||||
// variables
|
||||
variables: getVariables(oldDash),
|
||||
// EOF variables
|
||||
|
||||
// elements
|
||||
elements: getElements(oldDash),
|
||||
// EOF elements
|
||||
|
||||
// annotations
|
||||
annotations: [], //FIXME
|
||||
// EOF annotations
|
||||
|
||||
// layout
|
||||
layout: {
|
||||
kind: 'GridLayout',
|
||||
spec: {
|
||||
items: getGridLayoutItems(oldDash),
|
||||
},
|
||||
},
|
||||
// EOF layout
|
||||
};
|
||||
|
||||
if (isDashboardSchemaV2(dashboardSchemaV2)) {
|
||||
return dashboardSchemaV2;
|
||||
}
|
||||
console.error('Error transforming dashboard to schema v2');
|
||||
throw new Error('Error transforming dashboard to schema v2');
|
||||
}
|
||||
|
||||
function getCursorSync(state: DashboardSceneState) {
|
||||
const cursorSync = state.$behaviors?.find((b): b is behaviors.CursorSync => b instanceof behaviors.CursorSync)?.state
|
||||
.sync;
|
||||
|
||||
return transformCursorSynctoEnum(cursorSync);
|
||||
}
|
||||
|
||||
function getLiveNow(state: DashboardSceneState) {
|
||||
const liveNow =
|
||||
state.$behaviors?.find((b): b is behaviors.LiveNowTimer => b instanceof behaviors.LiveNowTimer)?.isEnabled ||
|
||||
undefined;
|
||||
// hack for validator
|
||||
if (liveNow === undefined) {
|
||||
return Boolean(defaultDashboardV2Spec().liveNow);
|
||||
}
|
||||
return Boolean(liveNow);
|
||||
}
|
||||
|
||||
function getGridLayoutItems(state: DashboardSceneState, isSnapshot?: boolean): GridLayoutItemKind[] {
|
||||
const body = state.body;
|
||||
const elements: GridLayoutItemKind[] = [];
|
||||
if (body instanceof DefaultGridLayoutManager) {
|
||||
for (const child of body.state.grid.state.children) {
|
||||
if (child instanceof DashboardGridItem) {
|
||||
// TODO: handle panel repeater scenario
|
||||
// if (child.state.variableName) {
|
||||
// panels = panels.concat(panelRepeaterToPanels(child, isSnapshot));
|
||||
// } else {
|
||||
elements.push(gridItemToGridLayoutItemKind(child, isSnapshot));
|
||||
// }
|
||||
}
|
||||
|
||||
// TODO: OLD transformer code
|
||||
// if (child instanceof SceneGridRow) {
|
||||
// // Skip repeat clones or when generating a snapshot
|
||||
// if (child.state.key!.indexOf('-clone-') > 0 && !isSnapshot) {
|
||||
// continue;
|
||||
// }
|
||||
// gridRowToSaveModel(child, panels, isSnapshot);
|
||||
// }
|
||||
}
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
export function gridItemToGridLayoutItemKind(gridItem: DashboardGridItem, isSnapshot = false): GridLayoutItemKind {
|
||||
let elementGridItem: GridLayoutItemKind | undefined;
|
||||
let x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0;
|
||||
|
||||
let gridItem_ = gridItem;
|
||||
|
||||
if (!(gridItem_.state.body instanceof VizPanel)) {
|
||||
throw new Error('DashboardGridItem body expected to be VizPanel');
|
||||
}
|
||||
|
||||
// Get the grid position and size
|
||||
height = (gridItem_.state.variableName ? gridItem_.state.itemHeight : gridItem_.state.height) ?? 0;
|
||||
x = gridItem_.state.x ?? 0;
|
||||
y = gridItem_.state.y ?? 0;
|
||||
width = gridItem_.state.width ?? 0;
|
||||
|
||||
// FIXME: which name should we use for the element reference, key or something else ?
|
||||
const elementName = gridItem_.state.body.state.key ?? 'DefaultName';
|
||||
elementGridItem = {
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x,
|
||||
y,
|
||||
width: width,
|
||||
height: height,
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: elementName,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (!elementGridItem) {
|
||||
throw new Error('Unsupported grid item type');
|
||||
}
|
||||
|
||||
return elementGridItem;
|
||||
}
|
||||
|
||||
function getElements(state: DashboardSceneState) {
|
||||
const panels = state.body.getVizPanels() ?? [];
|
||||
const panelsArray = panels.reduce((acc: PanelKind[], vizPanel: VizPanel) => {
|
||||
const elementSpec: PanelKind = {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
uid: vizPanel.state.key ?? '', // FIXME: why is key optional?
|
||||
title: vizPanel.state.title,
|
||||
description: vizPanel.state.description ?? '',
|
||||
links: getPanelLinks(vizPanel),
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: getVizPanelQueries(vizPanel),
|
||||
transformations: getVizPanelTransformations(vizPanel),
|
||||
queryOptions: getVizPanelQueryOptions(vizPanel),
|
||||
},
|
||||
},
|
||||
vizConfig: {
|
||||
kind: vizPanel.state.pluginId,
|
||||
spec: {
|
||||
pluginVersion: vizPanel.state.pluginVersion ?? '',
|
||||
options: vizPanel.state.options,
|
||||
fieldConfig: (vizPanel.state.fieldConfig as FieldConfigSource) ?? defaultFieldConfigSource(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
acc.push(elementSpec);
|
||||
return acc;
|
||||
}, []);
|
||||
// create elements
|
||||
|
||||
const elements = createElements(panelsArray);
|
||||
return elements;
|
||||
}
|
||||
|
||||
function getPanelLinks(panel: VizPanel): DashboardLink[] {
|
||||
const vizLinks = dashboardSceneGraph.getPanelLinks(panel);
|
||||
if (vizLinks) {
|
||||
return (vizLinks.state.rawLinks as DashboardLink[]) ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] {
|
||||
const queries: PanelQueryKind[] = [];
|
||||
const queryRunner = getQueryRunnerFor(vizPanel);
|
||||
const vizPanelQueries = queryRunner?.state.queries;
|
||||
const datasource = queryRunner?.state.datasource;
|
||||
|
||||
if (vizPanelQueries) {
|
||||
vizPanelQueries.forEach((query) => {
|
||||
const dataQuery: DataQueryKind = {
|
||||
kind: getDataQueryKind(query),
|
||||
spec: query,
|
||||
};
|
||||
const querySpec: PanelQuerySpec = {
|
||||
datasource: datasource ?? defaultDataSourceRef(),
|
||||
query: dataQuery,
|
||||
refId: query.refId,
|
||||
hidden: query.hidden,
|
||||
};
|
||||
queries.push({
|
||||
kind: 'PanelQuery',
|
||||
spec: querySpec,
|
||||
});
|
||||
});
|
||||
}
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function getDataQueryKind(query: SceneDataQuery): string {
|
||||
// If the query has a datasource, use the datasource type, otherwise use 'default'
|
||||
return query.datasource?.type ?? 'default';
|
||||
}
|
||||
|
||||
export function getDataQuerySpec(query: SceneDataQuery): Record<string, any> {
|
||||
const dataQuerySpec = {
|
||||
kind: getDataQueryKind(query),
|
||||
spec: query,
|
||||
};
|
||||
return dataQuerySpec;
|
||||
}
|
||||
|
||||
function getVizPanelTransformations(vizPanel: VizPanel): TransformationKind[] {
|
||||
let transformations: TransformationKind[] = [];
|
||||
const dataProvider = vizPanel.state.$data;
|
||||
if (dataProvider instanceof SceneDataTransformer) {
|
||||
const transformationList = dataProvider.state.transformations;
|
||||
if (transformationList.length === 0) {
|
||||
return [];
|
||||
}
|
||||
transformationList.forEach((transformationItem) => {
|
||||
const transformation = transformationItem as DataTransformerConfig;
|
||||
const transformationSpec: DataTransformerConfig = {
|
||||
id: transformation.id,
|
||||
disabled: transformation.disabled,
|
||||
filter: {
|
||||
id: transformation.filter?.id ?? '',
|
||||
options: transformation.filter?.options ?? {},
|
||||
},
|
||||
topic: transformation.topic,
|
||||
options: transformation.options,
|
||||
};
|
||||
|
||||
transformations.push({
|
||||
kind: transformation.id,
|
||||
spec: transformationSpec,
|
||||
});
|
||||
});
|
||||
}
|
||||
return transformations;
|
||||
}
|
||||
|
||||
function getVizPanelQueryOptions(vizPanel: VizPanel): QueryOptionsSpec {
|
||||
let queryOptions: QueryOptionsSpec = {};
|
||||
const queryRunner = getQueryRunnerFor(vizPanel);
|
||||
|
||||
if (queryRunner) {
|
||||
queryOptions.maxDataPoints = queryRunner.state.maxDataPoints;
|
||||
|
||||
if (queryRunner.state.cacheTimeout) {
|
||||
queryOptions.cacheTimeout = queryRunner.state.cacheTimeout;
|
||||
}
|
||||
|
||||
if (queryRunner.state.queryCachingTTL) {
|
||||
queryOptions.queryCachingTTL = queryRunner.state.queryCachingTTL;
|
||||
}
|
||||
if (queryRunner.state.minInterval) {
|
||||
queryOptions.interval = queryRunner.state.minInterval;
|
||||
}
|
||||
}
|
||||
|
||||
const panelTime = vizPanel.state.$timeRange;
|
||||
|
||||
if (panelTime instanceof PanelTimeRange) {
|
||||
queryOptions.timeFrom = panelTime.state.timeFrom;
|
||||
queryOptions.timeShift = panelTime.state.timeShift;
|
||||
}
|
||||
return queryOptions;
|
||||
}
|
||||
|
||||
function createElements(panels: PanelKind[]): Record<string, PanelKind> {
|
||||
return panels.reduce(
|
||||
(acc, panel) => {
|
||||
const key = panel.spec.uid;
|
||||
acc[key] = panel;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, PanelKind>
|
||||
);
|
||||
}
|
||||
|
||||
function getVariables(oldDash: DashboardSceneState) {
|
||||
const variablesSet = oldDash.$variables;
|
||||
|
||||
// variables is an array of all variables kind (union)
|
||||
let variables: Array<
|
||||
| QueryVariableKind
|
||||
| TextVariableKind
|
||||
| IntervalVariableKind
|
||||
| DatasourceVariableKind
|
||||
| CustomVariableKind
|
||||
| ConstantVariableKind
|
||||
| GroupByVariableKind
|
||||
| AdhocVariableKind
|
||||
> = [];
|
||||
|
||||
if (variablesSet instanceof SceneVariableSet) {
|
||||
variables = sceneVariablesSetToSchemaV2Variables(variablesSet);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
// Function to know if the dashboard transformed is a valid DashboardV2Spec
|
||||
function isDashboardSchemaV2(dash: any): dash is DashboardV2Spec {
|
||||
if (typeof dash !== 'object' || dash === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof dash.title !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.description !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.cursorSync !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (!Object.values(DashboardCursorSync).includes(dash.cursorSync)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.liveNow !== 'boolean') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.preload !== 'boolean') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.editable !== 'boolean') {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(dash.links)) {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(dash.tags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dash.id !== undefined && typeof dash.id !== 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Time settings
|
||||
if (typeof dash.timeSettings !== 'object' || dash.timeSettings === null) {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.timezone !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.from !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.to !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.autoRefresh !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(dash.timeSettings.autoRefreshIntervals)) {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(dash.timeSettings.quickRanges)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.hideTimepicker !== 'boolean') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.weekStart !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.timeSettings.fiscalYearStartMonth !== 'number') {
|
||||
return false;
|
||||
}
|
||||
if (dash.timeSettings.nowDelay !== undefined && typeof dash.timeSettings.nowDelay !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other sections
|
||||
if (!Array.isArray(dash.variables)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.elements !== 'object' || dash.elements === null) {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(dash.annotations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Layout
|
||||
if (typeof dash.layout !== 'object' || dash.layout === null) {
|
||||
return false;
|
||||
}
|
||||
if (dash.layout.kind !== 'GridLayout') {
|
||||
return false;
|
||||
}
|
||||
if (typeof dash.layout.spec !== 'object' || dash.layout.spec === null) {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(dash.layout.spec.items)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import { DashboardLink as DashboardLinkTypeV1 } from '@grafana/schema';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
DashboardLinkType,
|
||||
VariableRefresh,
|
||||
VariableHide,
|
||||
VariableSort,
|
||||
defaultVariableHide,
|
||||
defaultVariableSort,
|
||||
defaultVariableRefresh,
|
||||
defaultDashboardLinkType,
|
||||
defaultDashboardCursorSync,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
|
||||
|
||||
import {
|
||||
transformCursorSynctoEnum,
|
||||
transformDashboardLinksToEnums,
|
||||
transformVariableRefreshToEnum,
|
||||
transformVariableHideToEnum,
|
||||
transformSortVariableToEnum,
|
||||
} from './transformToV2TypesUtils';
|
||||
|
||||
describe('transformToV2TypesUtils', () => {
|
||||
describe('transformCursorSynctoEnum', () => {
|
||||
it('should return the correct enum value for cursor sync', () => {
|
||||
expect(transformCursorSynctoEnum(0)).toBe(DashboardCursorSync.Off);
|
||||
expect(transformCursorSynctoEnum(1)).toBe(DashboardCursorSync.Crosshair);
|
||||
expect(transformCursorSynctoEnum(2)).toBe(DashboardCursorSync.Tooltip);
|
||||
expect(transformCursorSynctoEnum(undefined)).toBe(defaultDashboardCursorSync());
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformDashboardLinksToEnums', () => {
|
||||
const links: DashboardLinkTypeV1[] = [
|
||||
{
|
||||
type: 'link',
|
||||
asDropdown: false,
|
||||
icon: '',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
tags: [],
|
||||
title: '',
|
||||
url: '',
|
||||
targetBlank: false,
|
||||
tooltip: '',
|
||||
},
|
||||
{
|
||||
type: 'dashboards',
|
||||
asDropdown: false,
|
||||
icon: '',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
tags: [],
|
||||
title: '',
|
||||
url: '',
|
||||
targetBlank: false,
|
||||
tooltip: '',
|
||||
},
|
||||
{
|
||||
// @ts-expect-error Testing invalid type
|
||||
type: 'non-valid-type',
|
||||
asDropdown: false,
|
||||
icon: '',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
tags: [],
|
||||
title: '',
|
||||
url: '',
|
||||
targetBlank: false,
|
||||
tooltip: '',
|
||||
},
|
||||
];
|
||||
|
||||
const transformedLinks = transformDashboardLinksToEnums(links);
|
||||
expect(transformedLinks[0].type).toBe(DashboardLinkType.Link);
|
||||
expect(transformedLinks[1].type).toBe(DashboardLinkType.Dashboards);
|
||||
expect(transformedLinks[2].type).toBe(defaultDashboardLinkType());
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformVariableRefreshToEnum', () => {
|
||||
it('should return the correct enum value for variable refresh', () => {
|
||||
expect(transformVariableRefreshToEnum(0)).toBe(VariableRefresh.Never);
|
||||
expect(transformVariableRefreshToEnum(1)).toBe(VariableRefresh.OnDashboardLoad);
|
||||
expect(transformVariableRefreshToEnum(2)).toBe(VariableRefresh.OnTimeRangeChanged);
|
||||
expect(transformVariableRefreshToEnum(undefined)).toBe(defaultVariableRefresh());
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformVariableHideToEnum', () => {
|
||||
it('should return the correct enum value for variable hide', () => {
|
||||
expect(transformVariableHideToEnum(0)).toBe(VariableHide.DontHide);
|
||||
expect(transformVariableHideToEnum(1)).toBe(VariableHide.HideLabel);
|
||||
expect(transformVariableHideToEnum(2)).toBe(VariableHide.HideVariable);
|
||||
expect(transformVariableHideToEnum(undefined)).toBe(defaultVariableHide());
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformSortVariableToEnum', () => {
|
||||
it('should return the correct enum value for variable sort', () => {
|
||||
expect(transformSortVariableToEnum(0)).toBe(VariableSort.Disabled);
|
||||
expect(transformSortVariableToEnum(1)).toBe(VariableSort.AlphabeticalAsc);
|
||||
expect(transformSortVariableToEnum(2)).toBe(VariableSort.AlphabeticalDesc);
|
||||
expect(transformSortVariableToEnum(3)).toBe(VariableSort.NumericalAsc);
|
||||
expect(transformSortVariableToEnum(4)).toBe(VariableSort.NumericalDesc);
|
||||
expect(transformSortVariableToEnum(undefined)).toBe(defaultVariableSort());
|
||||
});
|
||||
});
|
@ -0,0 +1,94 @@
|
||||
import {
|
||||
DashboardLink as DashboardLinkV1,
|
||||
VariableHide as VariableHideV1,
|
||||
VariableRefresh as VariableRefreshV1,
|
||||
VariableSort as VariableSortV1,
|
||||
DashboardCursorSync as DashboardCursorSyncV1,
|
||||
DashboardLinkType as DashboardLinkTypeV1,
|
||||
} from '@grafana/schema';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
defaultDashboardV2Spec,
|
||||
DashboardLinkType,
|
||||
DashboardLink,
|
||||
defaultVariableHide,
|
||||
defaultVariableRefresh,
|
||||
defaultVariableSort,
|
||||
VariableHide,
|
||||
VariableRefresh,
|
||||
VariableSort,
|
||||
defaultDashboardLinkType,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
|
||||
|
||||
export function transformCursorSynctoEnum(cursorSync?: DashboardCursorSyncV1): DashboardCursorSync {
|
||||
switch (cursorSync) {
|
||||
case 0:
|
||||
return DashboardCursorSync.Off;
|
||||
case 1:
|
||||
return DashboardCursorSync.Crosshair;
|
||||
case 2:
|
||||
return DashboardCursorSync.Tooltip;
|
||||
default:
|
||||
return defaultDashboardV2Spec().cursorSync;
|
||||
}
|
||||
}
|
||||
|
||||
function transformDashboardLinkTypeToEnum(linkType: DashboardLinkTypeV1): DashboardLinkType {
|
||||
switch (linkType) {
|
||||
case 'link':
|
||||
return DashboardLinkType.Link;
|
||||
case 'dashboards':
|
||||
return DashboardLinkType.Dashboards;
|
||||
default:
|
||||
return defaultDashboardLinkType();
|
||||
}
|
||||
}
|
||||
|
||||
export function transformDashboardLinksToEnums(links: DashboardLinkV1[]): DashboardLink[] {
|
||||
return links.map((link) => {
|
||||
return {
|
||||
...link,
|
||||
type: transformDashboardLinkTypeToEnum(link.type),
|
||||
};
|
||||
});
|
||||
}
|
||||
export function transformVariableRefreshToEnum(refresh?: VariableRefreshV1): VariableRefresh {
|
||||
switch (refresh) {
|
||||
case 0:
|
||||
return VariableRefresh.Never;
|
||||
case 1:
|
||||
return VariableRefresh.OnDashboardLoad;
|
||||
case 2:
|
||||
return VariableRefresh.OnTimeRangeChanged;
|
||||
default:
|
||||
return defaultVariableRefresh();
|
||||
}
|
||||
}
|
||||
export function transformVariableHideToEnum(hide?: VariableHideV1): VariableHide {
|
||||
switch (hide) {
|
||||
case 0:
|
||||
return VariableHide.DontHide;
|
||||
case 1:
|
||||
return VariableHide.HideLabel;
|
||||
case 2:
|
||||
return VariableHide.HideVariable;
|
||||
default:
|
||||
return defaultVariableHide();
|
||||
}
|
||||
}
|
||||
export function transformSortVariableToEnum(sort?: VariableSortV1): VariableSort {
|
||||
switch (sort) {
|
||||
case 0:
|
||||
return VariableSort.Disabled;
|
||||
case 1:
|
||||
return VariableSort.AlphabeticalAsc;
|
||||
case 2:
|
||||
return VariableSort.AlphabeticalDesc;
|
||||
case 3:
|
||||
return VariableSort.NumericalAsc;
|
||||
case 4:
|
||||
return VariableSort.NumericalDesc;
|
||||
default:
|
||||
return defaultVariableSort();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import saveAs from 'file-saver';
|
||||
import { useAsync } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes';
|
||||
import { Button, ClipboardButton, CodeEditor, Field, Modal, Stack, Switch } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
@ -10,6 +11,7 @@ import { shareDashboardType } from 'app/features/dashboard/components/ShareModal
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||
import { transformSceneToSaveModelSchemaV2 } from '../serialization/transformSceneToSaveModelSchemaV2';
|
||||
import { getVariablesCompatibility } from '../utils/getVariablesCompatibility';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
@ -19,6 +21,7 @@ import { SceneShareTabState, ShareView } from './types';
|
||||
export interface ShareExportTabState extends SceneShareTabState {
|
||||
isSharingExternally?: boolean;
|
||||
isViewingJSON?: boolean;
|
||||
isViewingJSONSchemaV2?: boolean;
|
||||
}
|
||||
|
||||
export class ShareExportTab extends SceneObjectBase<ShareExportTabState> implements ShareView {
|
||||
@ -51,6 +54,12 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> impleme
|
||||
});
|
||||
};
|
||||
|
||||
public onViewJSONSchemaV2 = () => {
|
||||
this.setState({
|
||||
isViewingJSONSchemaV2: !this.state.isViewingJSONSchemaV2,
|
||||
});
|
||||
};
|
||||
|
||||
public getClipboardText() {
|
||||
return;
|
||||
}
|
||||
@ -72,6 +81,23 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> impleme
|
||||
return exportable;
|
||||
};
|
||||
|
||||
public getExportableDashboardJsonSchemaV2 = async () => {
|
||||
// const { isSharingExternally } = this.state;
|
||||
const saveModel = transformSceneToSaveModelSchemaV2(getDashboardSceneFor(this));
|
||||
|
||||
// const exportable = isSharingExternally
|
||||
// ? await this._exporter.makeExportable(
|
||||
// new DashboardModel(saveModel, undefined, {
|
||||
// getVariablesFromState: () => {
|
||||
// return getVariablesCompatibility(window.__grafanaSceneContext);
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// : saveModel;
|
||||
|
||||
return saveModel;
|
||||
};
|
||||
|
||||
public onSaveAsFile = async () => {
|
||||
const dashboardJson = await this.getExportableDashboardJson();
|
||||
const dashboardJsonPretty = JSON.stringify(dashboardJson, null, 2);
|
||||
@ -94,8 +120,9 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> impleme
|
||||
}
|
||||
|
||||
function ShareExportTabRenderer({ model }: SceneComponentProps<ShareExportTab>) {
|
||||
const { isSharingExternally, isViewingJSON, modalRef } = model.useState();
|
||||
|
||||
const { isSharingExternally, isViewingJSON, modalRef, isViewingJSONSchemaV2 } = model.useState();
|
||||
// use dashboardSchemaV2 to show new button, this is for internal testing
|
||||
const shouldShowNewSchemaV2Button = config.featureToggles.dashboardSchemaV2 ?? false;
|
||||
const dashboardJson = useAsync(async () => {
|
||||
if (isViewingJSON) {
|
||||
const json = await model.getExportableDashboardJson();
|
||||
@ -105,11 +132,20 @@ function ShareExportTabRenderer({ model }: SceneComponentProps<ShareExportTab>)
|
||||
return '';
|
||||
}, [isViewingJSON]);
|
||||
|
||||
const dashboardJsonSchemaV2 = useAsync(async () => {
|
||||
if (isViewingJSONSchemaV2) {
|
||||
const json = await model.getExportableDashboardJsonSchemaV2();
|
||||
console.log('json v2', json);
|
||||
return JSON.stringify(json, null, 2);
|
||||
}
|
||||
return '';
|
||||
}, [isViewingJSONSchemaV2]);
|
||||
|
||||
const exportExternallyTranslation = t('share-modal.export.share-externally-label', `Export for sharing externally`);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isViewingJSON && (
|
||||
{!isViewingJSON && !isViewingJSONSchemaV2 && (
|
||||
<>
|
||||
<p>
|
||||
<Trans i18nKey="share-modal.export.info-text">Export this dashboard.</Trans>
|
||||
@ -137,13 +173,17 @@ function ShareExportTabRenderer({ model }: SceneComponentProps<ShareExportTab>)
|
||||
<Button variant="secondary" icon="brackets-curly" onClick={model.onViewJSON}>
|
||||
<Trans i18nKey="share-modal.export.view-button">View JSON</Trans>
|
||||
</Button>
|
||||
{shouldShowNewSchemaV2Button && (
|
||||
<Button variant="secondary" icon="brackets-curly" onClick={model.onViewJSONSchemaV2}>
|
||||
<Trans i18nKey="share-modal.export.view-button-schemav2">View JSON SchemaV2</Trans>
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="primary" icon="save" onClick={() => model.onSaveAsFile()}>
|
||||
<Trans i18nKey="share-modal.export.save-button">Save to file</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isViewingJSON && (
|
||||
<>
|
||||
<AutoSizer disableHeight>
|
||||
@ -161,7 +201,12 @@ function ShareExportTabRenderer({ model }: SceneComponentProps<ShareExportTab>)
|
||||
}
|
||||
|
||||
if (dashboardJson.loading) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Trans i18nKey="share-modal.export.loading">Loading...</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -186,6 +231,52 @@ function ShareExportTabRenderer({ model }: SceneComponentProps<ShareExportTab>)
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
)}
|
||||
{isViewingJSONSchemaV2 && (
|
||||
<>
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => {
|
||||
if (dashboardJsonSchemaV2.value) {
|
||||
return (
|
||||
<CodeEditor
|
||||
value={dashboardJsonSchemaV2.value ?? ''}
|
||||
language="json"
|
||||
showMiniMap={false}
|
||||
height="500px"
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (dashboardJsonSchemaV2.loading) {
|
||||
return (
|
||||
<div>
|
||||
<Trans i18nKey="share-modal.export.loading">Loading...</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
</AutoSizer>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" fill="outline" onClick={model.onViewJSONSchemaV2} icon="arrow-left">
|
||||
<Trans i18nKey="share-modal.export.back-button">Back to export config</Trans>
|
||||
</Button>
|
||||
<ClipboardButton
|
||||
variant="secondary"
|
||||
icon="copy"
|
||||
disabled={dashboardJson.loading}
|
||||
getText={() => dashboardJson.value ?? ''}
|
||||
>
|
||||
<Trans i18nKey="share-modal.view-json.copy-button">Copy to Clipboard</Trans>
|
||||
</ClipboardButton>
|
||||
<Button variant="primary" icon="save" disabled={dashboardJson.loading} onClick={() => model.onSaveAsFile()}>
|
||||
<Trans i18nKey="share-modal.export.save-button">Save to file</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -2649,9 +2649,11 @@
|
||||
"back-button": "Back to export config",
|
||||
"cancel-button": "Cancel",
|
||||
"info-text": "Export this dashboard.",
|
||||
"loading": "Loading...",
|
||||
"save-button": "Save to file",
|
||||
"share-externally-label": "Export for sharing externally",
|
||||
"view-button": "View JSON"
|
||||
"view-button": "View JSON",
|
||||
"view-button-schemav2": "View JSON SchemaV2"
|
||||
},
|
||||
"library": {
|
||||
"info": "Create library panel."
|
||||
|
@ -2649,9 +2649,11 @@
|
||||
"back-button": "ßäčĸ ŧő ęχpőřŧ čőʼnƒįģ",
|
||||
"cancel-button": "Cäʼnčęľ",
|
||||
"info-text": "Ēχpőřŧ ŧĥįş đäşĥþőäřđ.",
|
||||
"loading": "Ŀőäđįʼnģ...",
|
||||
"save-button": "Ŝävę ŧő ƒįľę",
|
||||
"share-externally-label": "Ēχpőřŧ ƒőř şĥäřįʼnģ ęχŧęřʼnäľľy",
|
||||
"view-button": "Vįęŵ ĴŜØŃ"
|
||||
"view-button": "Vįęŵ ĴŜØŃ",
|
||||
"view-button-schemav2": "Vįęŵ ĴŜØŃ ŜčĥęmäV2"
|
||||
},
|
||||
"library": {
|
||||
"info": "Cřęäŧę ľįþřäřy päʼnęľ."
|
||||
|
Loading…
Reference in New Issue
Block a user