diff --git a/.betterer.results b/.betterer.results index d45139329f8..7e495f6d882 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1969,6 +1969,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], + "public/app/features/dashboard-scene/edit-pane/VizPanelEditPaneBehavior.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + ], "public/app/features/dashboard-scene/embedding/EmbeddedDashboardTestPage.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -2092,7 +2097,10 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], [0, 0, 0, "No untranslated strings. Wrap text with ", "10"], [0, 0, 0, "No untranslated strings. Wrap text with ", "11"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "12"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "12"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "13"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "14"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "15"] ], "public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -2100,6 +2108,11 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/scene/PanelSearchLayout.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + ], "public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], diff --git a/.drone.yml b/.drone.yml index 9a9595e7701..07831c07af2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -193,6 +193,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -276,6 +277,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -383,6 +385,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -521,6 +524,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -618,6 +622,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -1062,6 +1067,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -1414,6 +1420,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -1538,6 +1545,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -2095,6 +2103,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -3791,6 +3800,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -4822,6 +4832,7 @@ steps: from_secret: github-app-installation-id GITHUB_APP_PRIVATE_KEY: from_secret: github-app-private-key + failure: ignore image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 name: github-app-generate-token volumes: @@ -5740,6 +5751,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: e97f7a0c3923b506dad6bf861bb1ea440a8f072ee3744742eec35e7278c3581c +hmac: 04ba0c9b8e69705a28a24ba03de14ece0b15c4b44f6262fcbc6a9ee874b5a9db ... diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8a020a54703..317d2b57729 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -67,6 +67,11 @@ reviewers: ["team:grafana/plugins-platform-frontend"], matchPackageNames: ["@locker/{/,}**"], }, + { + groupName: "augurs", + matchPackageNames: ["@bsull/augurs"], + reviewers: ["sd2k"], + }, ], pin: { enabled: false, diff --git a/.gitignore b/.gitignore index a5ae3592a84..ac5ad6f5f7d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ __debug_bin* /devenv/docker/blocks/saml-enterprise # This is the new place of the block, but I leave the previous here for a while /devenv/docker/blocks/auth/saml-enterprise +/devenv/docker/blocks/auth/signer /tmp tools/phantomjs/phantomjs diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index a50b6c21745..820aabfed79 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -4,8 +4,8 @@ go 1.23.1 require ( github.com/grafana/grafana-app-sdk v0.23.1 - k8s.io/apimachinery v0.31.1 - k8s.io/apiserver v0.31.1 + k8s.io/apimachinery v0.31.3 + k8s.io/apiserver v0.31.3 k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 ) @@ -26,6 +26,7 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -36,7 +37,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -49,43 +50,45 @@ require ( github.com/onsi/gomega v1.34.1 // indirect github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.etcd.io/bbolt v1.3.10 // indirect go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.31.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.6.0 // indirect + golang.org/x/tools v0.27.0 // indirect google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.31.1 // indirect - k8s.io/client-go v0.31.1 // indirect - k8s.io/component-base v0.31.1 // indirect + k8s.io/api v0.31.3 // indirect + k8s.io/client-go v0.31.3 // indirect + k8s.io/component-base v0.31.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 7b660a3834e..766cfbe43da 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -28,7 +28,7 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= @@ -48,7 +48,7 @@ github.com/grafana/grafana-app-sdk v0.23.1 h1:BRpUG0bA0oVxjthkmO2thuJBo3nbjaRSSm github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= 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= @@ -83,7 +83,7 @@ github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -94,8 +94,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= @@ -111,15 +110,13 @@ go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -127,7 +124,7 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -135,39 +132,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.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/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20240820151423-278611b39280 h1:oKt8r1ZvaPqBe3oeGTdyx1iNjuBS+VJcc9QdU1CD3d8= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= 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= @@ -180,14 +172,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= -k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= -k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= -k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= -k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= -k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= -k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= +k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apiserver v0.31.3 h1:+1oHTtCB+OheqFEz375D0IlzHZ5VeQKX1KGXnx+TTuY= +k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= +k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md b/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md index 2bfba846315..b46ef7b477d 100644 --- a/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md +++ b/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md @@ -239,6 +239,7 @@ guaranteed because plugin developers can override this functionality. The follow ### Unsupported - Graphite +- Dynatrace ### Unconfirmed @@ -258,7 +259,6 @@ guaranteed because plugin developers can override this functionality. The follow - Datadog - Dataset - Druid -- Dynatrace - GitHub - Google BigQuery - Grafana for YNAB diff --git a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md index 9f56def9011..bb1d2cd02aa 100644 --- a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md @@ -19,13 +19,18 @@ refs: destination: /docs/grafana//panels-visualizations/query-transform-data/calculation-types/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/calculation-types/ + configure-legends: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/configure-legend/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-legend/ --- # Pie chart A pie chart is a graph that displays data as segments of a circle proportional to the whole, making it look like a sliced pie. Each slice corresponds to a value or measurement. -{{< figure src="/static/img/docs/pie-chart-panel/pie-chart-example.png" max-width="1200px" lightbox="true" alt="Pie charts" >}} +![Pie chart visualizations](/media/docs/grafana/panels-visualizations/screenshot-pie-chart-v11.4.png) The pie chart visualization is ideal when you have data that adds up to a total and you want to show the proportion of each value compared to other slices, as well as to the whole of the pie. @@ -98,116 +103,85 @@ If you want to display only the values from a given field (or column), once the ![Pie chart visualization with multiple rows and columns showing values from one column](/media/docs/grafana/panels-visualizations/screenshot-grafana-12.1-pie-example6.png) -## Panel options - -{{< docs/shared lookup="visualizations/panel-options.md" source="grafana" version="" >}} - -## Value options - -Use the following options to refine the value in your visualization. - -### Show - -Choose how much information to show. +## Configuration options -- **Calculate -** Reduces each value to a single value per series. -- **All values -** Displays every value from a single series. +{{< docs/shared lookup="visualizations/config-options-intro.md" source="grafana" version="" >}} -### Calculation +### Panel options -Select a calculation to reduce each series when Calculate has been selected. For information about available calculations, refer to [Calculation types](ref:calculation-types). +{{< docs/shared lookup="visualizations/panel-options.md" source="grafana" version="" >}} -### Limit +### Value options -When displaying every value from a single series, this limits the number of values displayed. +Use the following options to refine the value in your visualization. -### Fields + -Select which field or fields to display in the visualization. Each field name is available on the list, or you can select one of the following options: +| Option | Description | +| ------ | ----------- | +| Show | Set how much information to show. Choose from:
  • **Calculate** - Reduces each value to a single value per series.
  • **All values** - Displays every value from a single series.
| +| Calculation | If you chose **Calculate** as your **Show** option, select a calculation to reduce each series. For information about available calculations, refer to [Calculation types](ref:calculation-types). | +| Limit | If you chose **All values** as your **Show** option, enter a value to limit the number of values displayed. | +| Fields | Select which field or fields to display in the visualization. Each field name is available on the list, or you can select one of the following options:
  • **Numeric fields** - All fields with numerical values.
  • **All fields** - All fields that are not removed by transformations.
  • **Time** - All fields with time values.
| -- **Numeric fields -** All fields with numerical values. -- **All fields -** All fields that are not removed by transformations. -- **Time -** All fields with time values. + -## Pie chart options +### Pie chart options Use these options to refine how your visualization looks. -### Pie chart type - -Select the pie chart display style. +#### Pie chart type -### Pie +Select the pie chart display style. Choose from **Pie** or **Donut**. -![Pie type chart](/static/img/docs/pie-chart-panel/pie-type-chart-7-5.png) +![Pie chart types](/media/docs/grafana/panels-visualizations/screenshot-pie-chart-types.png) -### Donut - -![Donut type chart](/static/img/docs/pie-chart-panel/donut-type-chart-7-5.png) - -### Labels +#### Labels Select labels to display on the pie chart. You can select more than one. -- **Name -** The series or field name. -- **Percent -** The percentage of the whole. -- **Value -** The raw numerical value. +- **Name** - The series or field name. +- **Percent** - The percentage of the whole. +- **Value** - The raw numerical value. Labels are displayed in white over the body of the chart. You might need to select darker chart colors to make them more visible. Long names or numbers might be clipped. -The following example shows a pie chart with **Name** and **Percent** labels displayed. - -![Pie chart labels](/static/img/docs/pie-chart-panel/pie-chart-labels-7-5.png) - -## Tooltip options - -{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} - -## Legend options - -Use these settings to define how the legend appears in your visualization. For more information about the legend, refer to [Configure a legend]({{< relref "../../configure-legend" >}}). - -### Visibility - -Toggle the switch to turn the legend on or off. - -### Mode - -Use these settings to define how the legend appears in your visualization. - -- **List -** Displays the legend as a list. This is a default display mode of the legend. -- **Table -** Displays the legend as a table. +The following example shows a pie chart with **Name** and **Percent** labels displayed: -### Placement +{{< figure src="/static/img/docs/pie-chart-panel/pie-chart-labels-7-5.png" alt="Pie chart labels" max-width="350px" >}} -Choose where to display the legend. +### Tooltip options -- **Bottom -** Below the graph. -- **Right -** To the right of the graph. +{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" leveloffset="+1" >}} -#### Width +### Legend options -Control how wide the legend is when placed on the right side of the visualization. This option is only displayed if you set the legend placement to **Right**. +Use these settings to define how the legend appears in your visualization. For more information about the legend, refer to [Configure a legend](ref:configure-legends). -### Values + -Select values to display in the legend. You can select more than one. +| Option | Description | +| ------ | ----------- | +| Visibility | Toggle the switch to turn the legend on or off. | +| Mode | Use these settings to define how the legend appears in your visualization. Choose from:
  • **List** - Displays the legend as a list. This is a default display mode of the legend.
  • **Table** - Displays the legend as a table.
| +| Placement | Select where to display the legend. Choose **Bottom** or **Right**. | +| Width | Control how wide the legend is when placed on the right side of the visualization. This option is only displayed if you set the legend placement to **Right**. | +| Legend values | Select values to display in the legend. You can select more than one:
  • **Percent** - The percentage of the whole.
  • **Value** - The raw numerical value.
| -- **Percent:** The percentage of the whole. -- **Value:** The raw numerical value. + -## Standard options +### Standard options {{< docs/shared lookup="visualizations/standard-options.md" source="grafana" version="" >}} -## Data links +### Data links {{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} -## Value mappings +### Value mappings {{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} -## Field overrides +### Field overrides {{< docs/shared lookup="visualizations/overrides-options.md" source="grafana" version="" >}} diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 3f7c27beb59..e628d8caa8d 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -70,13 +70,14 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes | | `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes | | `accessActionSets` | Introduces action sets for resource permissions. Also ensures that all folder editors and admins can create subfolders without needing any additional permissions. | Yes | -| `newDashboardSharingComponent` | Enables the new sharing drawer design | | +| `newDashboardSharingComponent` | Enables the new sharing drawer design | Yes | | `notificationBanner` | Enables the notification banner UI and API | Yes | | `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | | | `pinNavItems` | Enables pinning of nav items | Yes | | `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin | Yes | | `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes | | `cloudwatchMetricInsightsCrossAccount` | Enables cross account observability for Cloudwatch Metric Insights query builder | Yes | +| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes | | `singleTopNav` | Unifies the top search bar and breadcrumb bar into one | Yes | | `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | | | `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes | @@ -205,7 +206,6 @@ Experimental features might be changed or removed without prior notice. | `failWrongDSUID` | Throws an error if a datasource has an invalid UIDs | | `alertingApiServer` | Register Alerting APIs with the K8s API server | | `dataplaneAggregator` | Enable grafana dataplane aggregator | -| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | | `lokiSendDashboardPanelNames` | Send dashboard and panel names to Loki when querying | | `alertingPrometheusRulesPrimary` | Uses Prometheus rules as the primary source of truth for ruler-enabled data sources | | `exploreLogsShardSplitting` | Used in Explore Logs to split queries into multiple queries based on the number of shards | diff --git a/docs/sources/setup-grafana/installation/_index.md b/docs/sources/setup-grafana/installation/_index.md index dd6ad422c61..940a80aa91c 100644 --- a/docs/sources/setup-grafana/installation/_index.md +++ b/docs/sources/setup-grafana/installation/_index.md @@ -46,7 +46,7 @@ Installation of Grafana on other operating systems is possible, but is not recom Grafana requires the minimum system resources: - Minimum recommended memory: 512 MB -- Minimum recommended CPU: 1 +- Minimum recommended CPU: 1 core Some features might require more memory or CPUs, including: diff --git a/e2e/dashboards-suite/dashboard-public-templating.spec.ts b/e2e/dashboards-suite/dashboard-public-templating.spec.ts index 4a4e660d44e..8040f4ce56e 100644 --- a/e2e/dashboards-suite/dashboard-public-templating.spec.ts +++ b/e2e/dashboards-suite/dashboard-public-templating.spec.ts @@ -7,7 +7,7 @@ describe('Create a public dashboard with template variables shows a template var it('Create a public dashboard with template variables shows a template variable warning', () => { // Opening a dashboard with template variables - e2e.flows.openDashboard({ uid: 'HYaGDGIMk' }); + e2e.flows.openDashboard({ uid: 'HYaGDGIMk', queryParams: { '__feature.newDashboardSharingComponent': false } }); // Open sharing modal e2e.components.NavToolbar.shareDashboard().click(); diff --git a/e2e/dashboards-suite/snapshot-create.spec.ts b/e2e/dashboards-suite/snapshot-create.spec.ts index aff04b6b805..ee07081bfd0 100644 --- a/e2e/dashboards-suite/snapshot-create.spec.ts +++ b/e2e/dashboards-suite/snapshot-create.spec.ts @@ -10,7 +10,7 @@ describe('Snapshots', () => { cy.intercept({ pathname: '/api/ds/query', }).as('query'); - e2e.flows.openDashboard({ uid: 'ZqZnVvFZz' }); + e2e.flows.openDashboard({ uid: 'ZqZnVvFZz', queryParams: { '__feature.newDashboardSharingComponent': false } }); cy.wait('@query'); const panelsToCheck = [ diff --git a/go.mod b/go.mod index 077c92aba10..dcc684cee4c 100644 --- a/go.mod +++ b/go.mod @@ -478,6 +478,8 @@ require github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs require github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent +require github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7 // @grafana/alerting-backend + require ( cloud.google.com/go/longrunning v0.6.0 // indirect github.com/at-wat/mqtt-go v0.19.4 // indirect diff --git a/go.sum b/go.sum index 5a287842840..2f8b49fc192 100644 --- a/go.sum +++ b/go.sum @@ -2327,6 +2327,8 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/ github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= github.com/grafana/grafana-plugin-sdk-go v0.260.1 h1:KzbooQP9mv/9CPsn+SoUwGuomA8oUxO0iuIq6Rg/ekE= github.com/grafana/grafana-plugin-sdk-go v0.260.1/go.mod h1:JriieK5Oc5v120QKhMs/LO55N0P3YI2ttEiVT1wfYsw= +github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7 h1:JFB5dvs0XwBh/RiDNA5OrqcF3eWCQmTYBm6Hy79PDMQ= +github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7/go.mod h1:AVvGgNqHsruJINRjKkhhY5NZMh5ke6Ei2bywuQ4Uuus= github.com/grafana/grafana/apps/playlist v0.0.0-20241105090059-facca37f4d1f h1:zZN/Jy7PjoqtrMiBRV5O3x4xAArcSbUznuyAPACrKXI= github.com/grafana/grafana/apps/playlist v0.0.0-20241105090059-facca37f4d1f/go.mod h1:e97Zxn1WX4Wn9TXEvwTjMNwU6yrjX/K7uVNSCZyEwxY= github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 h1:2H9x4q53pkfUGtSNYD1qSBpNnxrFgylof/TYADb5xMI= diff --git a/go.work.sum b/go.work.sum index 0195001fd44..2ed4636f78b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1231,6 +1231,8 @@ github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWe github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= +github.com/moby/moby v26.0.0+incompatible h1:2n9/cIWkxiEI1VsWgTGgXhxIWUbv42PyxEP9L+RReC0= +github.com/moby/moby v26.0.0+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= @@ -1701,6 +1703,7 @@ go.opentelemetry.io/contrib/propagators/b3 v1.23.0/go.mod h1:Gyz7V7XghvwTq+mIhLF go.opentelemetry.io/contrib/propagators/b3 v1.27.0 h1:IjgxbomVrV9za6bRi8fWCNXENs0co37SZedQilP2hm0= go.opentelemetry.io/contrib/propagators/b3 v1.27.0/go.mod h1:Dv9obQz25lCisDvvs4dy28UPh974CxkahRDUPsY7y9E= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/bridge/opencensus v1.26.0 h1:DZzxj9QjznMVoehskOJnFP2gsTCWtDTFBDvFhPAY7nc= go.opentelemetry.io/otel/bridge/opencensus v1.26.0/go.mod h1:rJiX0KrF5m8Tm1XE8jLczpAv5zUaDcvhKecFG0ZoFG4= go.opentelemetry.io/otel/bridge/opencensus v1.27.0 h1:ao9aGGHd+G4YfjBpGs6vbkvt5hoC67STlJA9fCnOAcs= @@ -1745,6 +1748,7 @@ go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDH go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= @@ -1790,6 +1794,7 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -1803,12 +1808,14 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1875,6 +1882,7 @@ google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjr google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= diff --git a/package.json b/package.json index ed0d15ea660..4fa95ba0d77 100644 --- a/package.json +++ b/package.json @@ -251,7 +251,7 @@ "yargs": "^17.5.1" }, "dependencies": { - "@bsull/augurs": "^0.6.0", + "@bsull/augurs": "^0.7.0", "@emotion/css": "11.13.5", "@emotion/react": "11.13.5", "@fingerprintjs/fingerprintjs": "^3.4.2", @@ -274,8 +274,8 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "5.28.1", - "@grafana/scenes-react": "5.28.1", + "@grafana/scenes": "^5.30.0", + "@grafana/scenes-react": "^5.30.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index 45d597378c4..da5f2075564 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -34,6 +34,7 @@ interface BaseProps { menu?: ReactElement | (() => ReactElement); dragClass?: string; dragClassCancel?: string; + onDragStart?: (e: React.PointerEvent) => void; selectionId?: string; /** * Use only to indicate loading or streaming data in the panel. @@ -142,6 +143,7 @@ export function PanelChrome({ onFocus, onMouseMove, onMouseEnter, + onDragStart, showMenuAlways = false, }: PanelChromeProps) { const theme = useTheme2(); @@ -312,6 +314,7 @@ export function PanelChrome({ className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container" + onPointerDown={onDragStart} onPointerUp={onSelect} > {statusMessage && ( diff --git a/pkg/api/README.md b/pkg/api/README.md index 08358933378..b3aee79ff7c 100644 --- a/pkg/api/README.md +++ b/pkg/api/README.md @@ -12,6 +12,8 @@ Developers modifying the HTTP API endpoints need to make sure to add the necessa The following route defines a `PATCH` endpoint under the `/serviceaccounts/{serviceAccountId}` path with tag `service_accounts` (used for grouping together several routes) and operation ID `updateServiceAccount` (used for uniquely identifying routes and associate parameters and response with them). +> For enterprise endpoints make sure you add the `enterprise` tag as well. + ```go // swagger:route PATCH /serviceaccounts/{serviceAccountId} service_accounts updateServiceAccount diff --git a/pkg/build/cmd/publishaws.go b/pkg/build/cmd/publishaws.go index aa52ef6debc..f48f821a0ed 100644 --- a/pkg/build/cmd/publishaws.go +++ b/pkg/build/cmd/publishaws.go @@ -15,7 +15,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/marketplacecatalog" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" "github.com/urfave/cli/v2" @@ -60,9 +60,9 @@ type AwsMarketplacePublishingService struct { } type AwsMarketplaceDocker interface { - ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) + ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) ImageTag(ctx context.Context, source string, target string) error - ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) + ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) } type AwsMarketplaceRegistry interface { @@ -172,8 +172,8 @@ func (s *AwsMarketplacePublishingService) Login(ctx context.Context) error { return err } -func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, image string, version string) error { - reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", image, version), types.ImagePullOptions{ +func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, img string, version string) error { + reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", img, version), image.PullOptions{ Platform: imagePlatform, }) if err != nil { @@ -201,7 +201,7 @@ func (s *AwsMarketplacePublishingService) TagImage(ctx context.Context, image st } func (s *AwsMarketplacePublishingService) PushToMarketplace(ctx context.Context, repo string, version string) error { - reader, err := s.docker.ImagePush(ctx, fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version), types.ImagePushOptions{ + reader, err := s.docker.ImagePush(ctx, fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version), image.PushOptions{ RegistryAuth: s.auth, }) if err != nil { diff --git a/pkg/build/cmd/publishaws_test.go b/pkg/build/cmd/publishaws_test.go index fc8f2615336..626f5f4c5f4 100644 --- a/pkg/build/cmd/publishaws_test.go +++ b/pkg/build/cmd/publishaws_test.go @@ -14,7 +14,7 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/marketplacecatalog" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" ) @@ -168,13 +168,13 @@ type mockAwsMarketplaceDocker struct { ImagePushError error } -func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) { +func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePullError } func (m *mockAwsMarketplaceDocker) ImageTag(ctx context.Context, source string, target string) error { return m.ImageTagError } -func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) { +func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePushError } diff --git a/pkg/build/go.mod b/pkg/build/go.mod index cca4e0474e4..bde874811f5 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -5,7 +5,7 @@ go 1.23.1 // Override docker/docker to avoid: // go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires // github.com/docker/docker@v0.0.0-00010101000000-000000000000: invalid version: unknown revision 000000000000 -replace github.com/docker/docker => github.com/moby/moby v25.0.2+incompatible +replace github.com/docker/docker => github.com/moby/moby v26.0.0+incompatible // contains openapi encoder fixes. remove ASAP replace cuelang.org/go => github.com/grafana/cue v0.0.0-20230926092038-971951014e3f // @grafana/grafana-as-code @@ -103,6 +103,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/sosodev/duration v1.2.0 // indirect github.com/vektah/gqlparser/v2 v2.5.11 // indirect diff --git a/pkg/build/go.sum b/pkg/build/go.sum index b32d80c077c..7117ad2ce56 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -169,8 +169,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/moby/moby v25.0.2+incompatible h1:g2oKRI7vgWkiPHZbBghaPbcV/SuKP1g/YLx0I2nxFT4= -github.com/moby/moby v25.0.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/moby v26.0.0+incompatible h1:2n9/cIWkxiEI1VsWgTGgXhxIWUbv42PyxEP9L+RReC0= +github.com/moby/moby v26.0.0+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= diff --git a/pkg/server/module_server_test.go b/pkg/server/module_server_test.go index 272fe56c53d..ec7b97b1230 100644 --- a/pkg/server/module_server_test.go +++ b/pkg/server/module_server_test.go @@ -32,6 +32,10 @@ func TestIntegrationWillRunInstrumentationServerWhenTargetHasNoHttpServer(t *tes if dbType == "sqlite3" { t.Skip("skipping - sqlite not supported for storage server target") } + // TODO - fix this test for postgres + if dbType == "postgres" { + t.Skip("skipping - test not working with postgres in Drone. Works locally.") + } _, cfg := db.InitTestDBWithCfg(t) cfg.HTTPPort = "3001" diff --git a/pkg/services/accesscontrol/dualwrite/reconciler.go b/pkg/services/accesscontrol/dualwrite/reconciler.go index 2be38d90aac..930327340fb 100644 --- a/pkg/services/accesscontrol/dualwrite/reconciler.go +++ b/pkg/services/accesscontrol/dualwrite/reconciler.go @@ -55,19 +55,19 @@ func NewZanzanaReconciler(cfg *setting.Cfg, client zanzana.Client, store db.DB, newResourceReconciler( "managed folder permissions", managedPermissionsCollector(store, zanzana.KindFolders), - zanzanaCollector(zanzana.FolderRelations), + zanzanaCollector(zanzana.RelationsFolder), client, ), newResourceReconciler( "managed dashboard permissions", managedPermissionsCollector(store, zanzana.KindDashboards), - zanzanaCollector(zanzana.ResourceRelations), + zanzanaCollector(zanzana.RelationsResouce), client, ), newResourceReconciler( "role permissions", rolePermissionsCollector(store), - zanzanaCollector(zanzana.FolderRelations), + zanzanaCollector(zanzana.RelationsFolder), client, ), newResourceReconciler( diff --git a/pkg/services/authz/zanzana/common/info.go b/pkg/services/authz/zanzana/common/info.go index 0517a0be84a..5ec718d9933 100644 --- a/pkg/services/authz/zanzana/common/info.go +++ b/pkg/services/authz/zanzana/common/info.go @@ -11,11 +11,15 @@ type TypeInfo struct { Relations []string } +func (t TypeInfo) IsValidRelation(relation string) bool { + return isValidRelation(relation, t.Relations) +} + var typedResources = map[string]TypeInfo{ FormatGroupResource( folderalpha1.FolderResourceInfo.GroupResource().Group, folderalpha1.FolderResourceInfo.GroupResource().Resource, - ): {Type: "folder", Relations: append(ResourceRelations, RelationCreate)}, + ): {Type: "folder", Relations: RelationsFolder}, } func GetTypeInfo(group, resource string) (TypeInfo, bool) { @@ -24,19 +28,19 @@ func GetTypeInfo(group, resource string) (TypeInfo, bool) { } var VerbMapping = map[string]string{ - utils.VerbGet: RelationRead, - utils.VerbList: RelationRead, - utils.VerbWatch: RelationRead, + utils.VerbGet: RelationGet, + utils.VerbList: RelationGet, + utils.VerbWatch: RelationGet, utils.VerbCreate: RelationCreate, - utils.VerbUpdate: RelationWrite, - utils.VerbPatch: RelationWrite, + utils.VerbUpdate: RelationUpdate, + utils.VerbPatch: RelationUpdate, utils.VerbDelete: RelationDelete, utils.VerbDeleteCollection: RelationDelete, } var RelationToVerbMapping = map[string]string{ - RelationRead: utils.VerbGet, + RelationGet: utils.VerbGet, RelationCreate: utils.VerbCreate, - RelationWrite: utils.VerbUpdate, + RelationUpdate: utils.VerbUpdate, RelationDelete: utils.VerbDelete, } diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index 294427631a8..cc94052438c 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -16,9 +16,12 @@ const ( TypeRenderService string = "render" TypeTeam string = "team" TypeRole string = "role" - TypeFolder string = "folder" - TypeResource string = "resource" - TypeNamespace string = "namespace" +) + +const ( + TypeFolder string = "folder" + TypeResource string = "resource" + TypeGroupResouce string = "group_resource" ) const ( @@ -31,44 +34,74 @@ const ( RelationSetEdit string = "edit" RelationSetAdmin string = "admin" - RelationRead string = "read" - RelationWrite string = "write" - RelationCreate string = "create" - RelationDelete string = "delete" - RelationPermissionsRead string = "permissions_read" - RelationPermissionsWrite string = "permissions_write" + RelationGet string = "get" + RelationUpdate string = "update" + RelationCreate string = "create" + RelationDelete string = "delete" RelationFolderResourceSetView string = "resource_" + RelationSetView RelationFolderResourceSetEdit string = "resource_" + RelationSetEdit RelationFolderResourceSetAdmin string = "resource_" + RelationSetAdmin - RelationFolderResourceRead string = "resource_" + RelationRead - RelationFolderResourceWrite string = "resource_" + RelationWrite - RelationFolderResourceCreate string = "resource_" + RelationCreate - RelationFolderResourceDelete string = "resource_" + RelationDelete - RelationFolderResourcePermissionsRead string = "resource_" + RelationPermissionsRead - RelationFolderResourcePermissionsWrite string = "resource_" + RelationPermissionsWrite + RelationFolderResourceGet string = "resource_" + RelationGet + RelationFolderResourceUpdate string = "resource_" + RelationUpdate + RelationFolderResourceCreate string = "resource_" + RelationCreate + RelationFolderResourceDelete string = "resource_" + RelationDelete ) -var ResourceRelations = []string{ - RelationRead, - RelationWrite, +// RelationsGroupResource are relations that can be added on type "group_resource". +var RelationsGroupResource = []string{ + RelationGet, + RelationUpdate, + RelationCreate, RelationDelete, - RelationPermissionsRead, - RelationPermissionsWrite, } -var FolderRelations = append( - ResourceRelations, - RelationCreate, - RelationFolderResourceRead, - RelationFolderResourceWrite, +// RelationsResource are relations that can be added on type "resource". +var RelationsResource = []string{ + RelationGet, + RelationUpdate, + RelationDelete, +} + +// RelationsFolderResource are relations that can be added on type "folder" for child resources. +var RelationsFolderResource = []string{ + RelationFolderResourceGet, + RelationFolderResourceUpdate, RelationFolderResourceCreate, RelationFolderResourceDelete, - RelationFolderResourcePermissionsRead, - RelationFolderResourcePermissionsWrite, +} + +// RelationsFolder are relations that can be added on type "folder". +var RelationsFolder = append( + RelationsFolderResource, + RelationGet, + RelationUpdate, + RelationCreate, + RelationDelete, ) +func IsGroupResourceRelation(relation string) bool { + return isValidRelation(relation, RelationsGroupResource) +} + +func IsFolderResourceRelation(relation string) bool { + return isValidRelation(relation, RelationsFolderResource) +} + +func IsResourceRelation(relation string) bool { + return isValidRelation(relation, RelationsResource) +} + +func isValidRelation(relation string, valid []string) bool { + for _, r := range valid { + if r == relation { + return true + } + } + return false +} + func FolderResourceRelation(relation string) string { return fmt.Sprintf("%s_%s", TypeResource, relation) } @@ -85,8 +118,8 @@ func NewFolderIdent(name string) string { return fmt.Sprintf("%s:%s", TypeFolder, name) } -func NewNamespaceResourceIdent(group, resource string) string { - return fmt.Sprintf("%s:%s", TypeNamespace, FormatGroupResource(group, resource)) +func NewGroupResourceIdent(group, resource string) string { + return fmt.Sprintf("%s:%s", TypeGroupResouce, FormatGroupResource(group, resource)) } func FormatGroupResource(group, resource string) string { @@ -139,11 +172,11 @@ func NewFolderResourceTuple(subject, relation, group, resource, folder string) * } } -func NewNamespaceResourceTuple(subject, relation, group, resource string) *openfgav1.TupleKey { +func NewGroupResourceTuple(subject, relation, group, resource string) *openfgav1.TupleKey { return &openfgav1.TupleKey{ User: subject, Relation: relation, - Object: NewNamespaceResourceIdent(group, resource), + Object: NewGroupResourceIdent(group, resource), } } @@ -258,10 +291,18 @@ func AddRenderContext(req *openfgav1.CheckRequest) { req.ContextualTuples.TupleKeys = append(req.ContextualTuples.TupleKeys, &openfgav1.TupleKey{ User: req.TupleKey.User, - Relation: "view", - Object: NewNamespaceResourceIdent( + Relation: RelationSetView, + Object: NewGroupResourceIdent( dashboardalpha1.DashboardResourceInfo.GroupResource().Group, dashboardalpha1.DashboardResourceInfo.GroupResource().Resource, ), }) } + +func NewResourceContext(group, resource string) *structpb.Struct { + return &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "requested_group": structpb.NewStringValue(FormatGroupResource(group, resource)), + }, + } +} diff --git a/pkg/services/authz/zanzana/schema/README.md b/pkg/services/authz/zanzana/schema/README.md index e2e82966b36..4462e0f18e0 100644 --- a/pkg/services/authz/zanzana/schema/README.md +++ b/pkg/services/authz/zanzana/schema/README.md @@ -2,10 +2,10 @@ Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/modeling/getting-started) (schema) using to model access control in Grafana. -## Namespace level permissions +## GroupResource level permissions -A relation to a namespace object grant access to all objects of the GroupResource in the entire namespace. -They take the form of `{ “user”: “user:1”, relation: “read”, object:”namespace:dashboard.grafana.app/dashboard” }`. This +A relation to a group_resource object grants access to all objects of the GroupResource. +They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboard” }`. This example would grant `user:1` access to all `dashboard.grafana.app/dashboard` in the namespace. ## Folder level permissions diff --git a/pkg/services/authz/zanzana/schema/schema_core.fga b/pkg/services/authz/zanzana/schema/schema_core.fga index 86a3a212361..8dd5f382279 100644 --- a/pkg/services/authz/zanzana/schema/schema_core.fga +++ b/pkg/services/authz/zanzana/schema/schema_core.fga @@ -6,31 +6,15 @@ type service-account type render -type namespace - relations - define view: [user, service-account, render, team#member, role#assignee] or edit - define edit: [user, service-account, team#member, role#assignee] or admin - define admin: [user, service-account, team#member, role#assignee] - - define read: [user, service-account, render, team#member, role#assignee] or view - define create: [user, service-account, team#member, role#assignee] or edit - define write: [user, service-account, team#member, role#assignee] or edit - define delete: [user, service-account, team#member, role#assignee] or edit - define permissions_read: [user, service-account, team#member, role#assignee] or admin - define permissions_write: [user, service-account, team#member, role#assignee] or admin - type role relations define assignee: [user, service-account, team#member, role#assignee] type team relations - # Action sets define admin: [user, service-account] define member: [user, service-account] or admin - define read: [role#assignee] or member - define write: [role#assignee] or admin + define get: [role#assignee] or member + define update: [role#assignee] or admin define delete: [role#assignee] or admin - define permissions_read: [role#assignee] or admin - define permissions_write: [role#assignee] or admin diff --git a/pkg/services/authz/zanzana/schema/schema_folder.fga b/pkg/services/authz/zanzana/schema/schema_folder.fga index 117bff01199..d3cbb26bb64 100644 --- a/pkg/services/authz/zanzana/schema/schema_folder.fga +++ b/pkg/services/authz/zanzana/schema/schema_folder.fga @@ -9,9 +9,7 @@ type folder define edit: [user, service-account, team#member, role#assignee] or admin or edit from parent define admin: [user, service-account, team#member, role#assignee] or admin from parent - define read: [user, service-account, team#member, role#assignee] or view or read from parent + define get: [user, service-account, team#member, role#assignee] or view or get from parent define create: [user, service-account, team#member, role#assignee] or edit or create from parent - define write: [user, service-account, team#member, role#assignee] or edit or write from parent + define update: [user, service-account, team#member, role#assignee] or edit or update from parent define delete: [user, service-account, team#member, role#assignee] or edit or delete from parent - define permissions_read: [user, service-account, team#member, role#assignee] or admin or permissions_read from parent - define permissions_write: [user, service-account, team#member, role#assignee] or admin or permissions_write from parent diff --git a/pkg/services/authz/zanzana/schema/schema_resource.fga b/pkg/services/authz/zanzana/schema/schema_resource.fga index 9e90bf0472b..b97831d2163 100644 --- a/pkg/services/authz/zanzana/schema/schema_resource.fga +++ b/pkg/services/authz/zanzana/schema/schema_resource.fga @@ -6,12 +6,21 @@ extend type folder define resource_edit: [user, service-account, team#member, role#assignee] or resource_admin or resource_edit from parent define resource_admin: [user, service-account, team#member, role#assignee] or resource_admin from parent - define resource_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_read from parent + define resource_get: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_get from parent define resource_create: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent - define resource_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_write from parent + define resource_update: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_update from parent define resource_delete: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent - define resource_permissions_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_read from parent - define resource_permissions_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_write from parent + +type group_resource + relations + define view: [user, service-account, render, team#member, role#assignee] or edit + define edit: [user, service-account, team#member, role#assignee] or admin + define admin: [user, service-account, team#member, role#assignee] + + define get: [user, service-account, render, team#member, role#assignee] or view + define create: [user, service-account, team#member, role#assignee] or edit + define update: [user, service-account, team#member, role#assignee] or edit + define delete: [user, service-account, team#member, role#assignee] or edit type resource relations @@ -19,11 +28,9 @@ type resource define edit: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin define admin: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] - define read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view - define write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define get: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view + define update: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit define delete: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define permissions_read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin - define permissions_write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin condition group_filter(requested_group: string, group_resource: string) { requested_group == group_resource diff --git a/pkg/services/authz/zanzana/server/server_batch_check.go b/pkg/services/authz/zanzana/server/server_batch_check.go index 86b78634a56..46ae37189a7 100644 --- a/pkg/services/authz/zanzana/server/server_batch_check.go +++ b/pkg/services/authz/zanzana/server/server_batch_check.go @@ -56,7 +56,7 @@ func (s *Server) batchCheckItem( allowed, ok := groupResourceAccess[groupResource] if !ok { - res, err := s.checkNamespace(ctx, r.GetSubject(), relation, item.GetGroup(), item.GetResource(), store) + res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, item.GetGroup(), item.GetResource(), store) if err != nil { return nil, err } diff --git a/pkg/services/authz/zanzana/server/server_batch_check_test.go b/pkg/services/authz/zanzana/server/server_batch_check_test.go index ae8390ba951..5da2d2f76ee 100644 --- a/pkg/services/authz/zanzana/server/server_batch_check_test.go +++ b/pkg/services/authz/zanzana/server/server_batch_check_test.go @@ -44,7 +44,7 @@ func testBatchCheck(t *testing.T, server *Server) { assert.False(t, res.Groups[groupResource].Items["2"]) }) - t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through namespace", func(t *testing.T) { + t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through group_resource", func(t *testing.T) { groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) res, err := server.BatchCheck(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ {Name: "1", Folder: "1"}, @@ -108,7 +108,7 @@ func testBatchCheck(t *testing.T, server *Server) { assert.False(t, res.Groups[groupResource].Items["2"]) }) - t.Run("user:7 should be able to read folder {1,2} through namespace access", func(t *testing.T) { + t.Run("user:7 should be able to read folder {1,2} through group_resource access", func(t *testing.T) { groupResource := zanzana.FormatGroupResource(folderGroup, folderResource) res, err := server.BatchCheck(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, []*authzextv1.BatchCheckItem{ {Name: "1"}, diff --git a/pkg/services/authz/zanzana/server/server_capabilities.go b/pkg/services/authz/zanzana/server/server_capabilities.go index 4d63841137d..f80ea3c5fb2 100644 --- a/pkg/services/authz/zanzana/server/server_capabilities.go +++ b/pkg/services/authz/zanzana/server/server_capabilities.go @@ -20,9 +20,9 @@ func (s *Server) Capabilities(ctx context.Context, r *authzextv1.CapabilitiesReq } func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.CapabilitiesRequest, info common.TypeInfo, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) { - out := make([]string, 0, len(common.ResourceRelations)) + out := make([]string, 0, len(common.RelationsResource)) for _, relation := range info.Relations { - res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) + res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) if err != nil { return nil, err } @@ -46,9 +46,9 @@ func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.Capabiliti } func (s *Server) capabilitiesGeneric(ctx context.Context, r *authzextv1.CapabilitiesRequest, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) { - out := make([]string, 0, len(common.ResourceRelations)) - for _, relation := range common.ResourceRelations { - res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) + out := make([]string, 0, len(common.RelationsResource)) + for _, relation := range common.RelationsResource { + res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) if err != nil { return nil, err } diff --git a/pkg/services/authz/zanzana/server/server_capabilities_test.go b/pkg/services/authz/zanzana/server/server_capabilities_test.go index c1d43d8a61a..f12bd2ea6d1 100644 --- a/pkg/services/authz/zanzana/server/server_capabilities_test.go +++ b/pkg/services/authz/zanzana/server/server_capabilities_test.go @@ -26,42 +26,42 @@ func testCapabilities(t *testing.T, server *Server) { t.Run("user:1 should only be able to read and write resource:dashboards.grafana.app/dashboards/1", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:1", dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead, common.RelationWrite}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet, common.RelationUpdate}, res.GetCapabilities()) }) - t.Run("user:2 should be able to read and write resource:dashboards.grafana.app/dashboards/1 through namespace", func(t *testing.T) { + t.Run("user:2 should be able to read and write resource:dashboards.grafana.app/dashboards/1 through group_resource", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:2", dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead, common.RelationWrite}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet, common.RelationUpdate}, res.GetCapabilities()) }) t.Run("user:3 should be able to read resource:dashboards.grafana.app/dashboards/1 with set relation", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:3", dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities()) }) t.Run("user:4 should be able to read dashboards.grafana.app/dashboards in folder 1", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:4", dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities()) }) t.Run("user:5 should be able to read, write, create and delete resource:dashboards.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:5", dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead, common.RelationWrite, common.RelationDelete}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet, common.RelationUpdate, common.RelationDelete}, res.GetCapabilities()) }) - t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) { + t.Run("user:6 should be able to read folder 1", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:6", folderGroup, folderResource, "", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities()) }) - t.Run("user:7 should be able to read folder one through namespace access", func(t *testing.T) { + t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:7", folderGroup, folderResource, "", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationGet}, res.GetCapabilities()) }) } diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go index 792a9561760..f7e2f489a1b 100644 --- a/pkg/services/authz/zanzana/server/server_check.go +++ b/pkg/services/authz/zanzana/server/server_check.go @@ -7,7 +7,6 @@ import ( authzv1 "github.com/grafana/authlib/authz/proto/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1" - "google.golang.org/protobuf/types/known/structpb" "github.com/grafana/grafana/pkg/services/authz/zanzana/common" ) @@ -22,9 +21,7 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C } relation := common.VerbMapping[r.GetVerb()] - - // Check if subject has access through namespace - res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) + res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) if err != nil { return nil, err } @@ -39,18 +36,23 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C return s.checkGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), r.GetName(), r.GetFolder(), store) } -// checkTyped performes check on the root "namespace". If subject has access through the namespace they have access to -// every resource for that "GroupResource". -func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.CheckResponse, error) { +// checkGroupResource check if subject has access to the full "GroupResource", if they do they can access every object +// within it. +func (s *Server) checkGroupResource(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzv1.CheckResponse, error) { + if !common.IsGroupResourceRelation(relation) { + return &authzv1.CheckResponse{Allowed: false}, nil + } + req := &openfgav1.CheckRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, TupleKey: &openfgav1.CheckRequestTupleKey{ User: subject, Relation: relation, - Object: common.NewNamespaceResourceIdent(group, resource), + Object: common.NewGroupResourceIdent(group, resource), }, } + if strings.HasPrefix(subject, fmt.Sprintf("%s:", common.TypeRenderService)) { common.AddRenderContext(req) } @@ -63,8 +65,12 @@ func (s *Server) checkNamespace(ctx context.Context, subject, relation, group, r return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil } -// checkTyped performes checks on our typed resources e.g. folder. +// checkTyped checks on our typed resources e.g. folder. func (s *Server) checkTyped(ctx context.Context, subject, relation, name string, info common.TypeInfo, store *storeInfo) (*authzv1.CheckResponse, error) { + if !info.IsValidRelation(relation) { + return &authzv1.CheckResponse{Allowed: false}, nil + } + // Check if subject has direct access to resource res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ StoreId: store.ID, @@ -86,27 +92,26 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation, name string, return &authzv1.CheckResponse{Allowed: false}, nil } -// checkGeneric check our generic "resource" type. +// checkGeneric check our generic "resource" type. It checks: +// 1. If subject has access as a sub resource for a folder. +// 2. If subject has direct access to resource. func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, resource, name, folder string, store *storeInfo) (*authzv1.CheckResponse, error) { - groupResource := structpb.NewStringValue(common.FormatGroupResource(group, resource)) + var ( + resourceCtx = common.NewResourceContext(group, resource) + folderRelation = common.FolderResourceRelation(relation) + ) - // Create relation can only exist on namespace or folder level. - // So we skip direct resource access check. - if relation != common.RelationCreate { - // Check if subject has direct access to resource + if folder != "" && common.IsFolderResourceRelation(folderRelation) { + // Check if subject has access as a sub resource for the folder res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, TupleKey: &openfgav1.CheckRequestTupleKey{ User: subject, - Relation: relation, - Object: common.NewResourceIdent(group, resource, name), - }, - Context: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "requested_group": groupResource, - }, + Relation: common.FolderResourceRelation(relation), + Object: common.NewFolderIdent(folder), }, + Context: resourceCtx, }) if err != nil { @@ -114,28 +119,24 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, res } if res.GetAllowed() { - return &authzv1.CheckResponse{Allowed: true}, nil + return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil } } - if folder == "" { + if !common.IsResourceRelation(relation) { return &authzv1.CheckResponse{Allowed: false}, nil } - // Check if subject has access as a sub resource for the folder + // Check if subject has direct access to resource res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, TupleKey: &openfgav1.CheckRequestTupleKey{ User: subject, - Relation: common.FolderResourceRelation(relation), - Object: common.NewFolderIdent(folder), - }, - Context: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "requested_group": groupResource, - }, + Relation: relation, + Object: common.NewResourceIdent(group, resource, name), }, + Context: resourceCtx, }) if err != nil { diff --git a/pkg/services/authz/zanzana/server/server_check_test.go b/pkg/services/authz/zanzana/server/server_check_test.go index 6d4c6e49a15..0177b8e22a3 100644 --- a/pkg/services/authz/zanzana/server/server_check_test.go +++ b/pkg/services/authz/zanzana/server/server_check_test.go @@ -35,7 +35,7 @@ func testCheck(t *testing.T, server *Server) { assert.False(t, res.GetAllowed()) }) - t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through namespace", func(t *testing.T) { + t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through group_resource", func(t *testing.T) { res, err := server.Check(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) @@ -83,7 +83,7 @@ func testCheck(t *testing.T, server *Server) { assert.True(t, res.GetAllowed()) }) - t.Run("user:7 should be able to read folder one through namespace access", func(t *testing.T) { + t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) { res, err := server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) diff --git a/pkg/services/authz/zanzana/server/server_list.go b/pkg/services/authz/zanzana/server/server_list.go index 513ebb2490c..0baa35ba020 100644 --- a/pkg/services/authz/zanzana/server/server_list.go +++ b/pkg/services/authz/zanzana/server/server_list.go @@ -6,7 +6,6 @@ import ( "strings" openfgav1 "github.com/openfga/api/proto/openfga/v1" - "google.golang.org/protobuf/types/known/structpb" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" "github.com/grafana/grafana/pkg/services/authz/zanzana/common" @@ -23,15 +22,7 @@ func (s *Server) List(ctx context.Context, r *authzextv1.ListRequest) (*authzext relation := common.VerbMapping[r.GetVerb()] - res, err := s.checkNamespace( - ctx, - r.GetSubject(), - relation, - r.GetGroup(), - r.GetResource(), - store, - ) - + res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) if err != nil { return nil, err } @@ -55,8 +46,12 @@ func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequ } func (s *Server) listTyped(ctx context.Context, subject, relation string, info common.TypeInfo, store *storeInfo) (*authzextv1.ListResponse, error) { + if !info.IsValidRelation(relation) { + return &authzextv1.ListResponse{}, nil + } + // List all resources user has access too - listRes, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ + res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, Type: info.Type, @@ -68,50 +63,56 @@ func (s *Server) listTyped(ctx context.Context, subject, relation string, info c } return &authzextv1.ListResponse{ - Items: typedObjects(info.Type, listRes.GetObjects()), + Items: typedObjects(info.Type, res.GetObjects()), }, nil } func (s *Server) listGeneric(ctx context.Context, subject, relation, group, resource string, store *storeInfo) (*authzextv1.ListResponse, error) { - groupResource := structpb.NewStringValue(common.FormatGroupResource(group, resource)) + var ( + resourceCtx = common.NewResourceContext(group, resource) + folderRelation = common.FolderResourceRelation(relation) + ) // 1. List all folders subject has access to resource type in - folders, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - Type: common.TypeFolder, - Relation: common.FolderResourceRelation(relation), - User: subject, - Context: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "requested_group": groupResource, - }, - }, - }) - if err != nil { - return nil, err + var folders []string + if common.IsFolderResourceRelation(folderRelation) { + res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ + StoreId: store.ID, + AuthorizationModelId: store.ModelID, + Type: common.TypeFolder, + Relation: folderRelation, + User: subject, + Context: resourceCtx, + }) + + if err != nil { + return nil, err + } + + folders = res.GetObjects() } // 2. List all resource directly assigned to subject - direct, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - Type: common.TypeResource, - Relation: relation, - User: subject, - Context: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "requested_group": groupResource, - }, - }, - }) - if err != nil { - return nil, err + var resources []string + if common.IsResourceRelation(relation) { + res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ + StoreId: store.ID, + AuthorizationModelId: store.ModelID, + Type: common.TypeResource, + Relation: relation, + User: subject, + Context: resourceCtx, + }) + if err != nil { + return nil, err + } + + resources = res.GetObjects() } return &authzextv1.ListResponse{ - Folders: folderObject(folders.GetObjects()), - Items: directObjects(group, resource, direct.GetObjects()), + Folders: folderObject(folders), + Items: directObjects(group, resource, resources), }, nil } diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index 595da83ad0a..a3b39913091 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -80,19 +80,19 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server { AuthorizationModelId: storeInf.ModelID, Writes: &openfgav1.WriteRequestWrites{ TupleKeys: []*openfgav1.TupleKey{ - common.NewResourceTuple("user:1", "read", dashboardGroup, dashboardResource, "1"), - common.NewResourceTuple("user:1", "write", dashboardGroup, dashboardResource, "1"), - common.NewNamespaceResourceTuple("user:2", "read", dashboardGroup, dashboardResource), - common.NewNamespaceResourceTuple("user:2", "write", dashboardGroup, dashboardResource), - common.NewResourceTuple("user:3", "view", dashboardGroup, dashboardResource, "1"), - common.NewFolderResourceTuple("user:4", "read", dashboardGroup, dashboardResource, "1"), - common.NewFolderResourceTuple("user:4", "read", dashboardGroup, dashboardResource, "3"), - common.NewFolderResourceTuple("user:5", "edit", dashboardGroup, dashboardResource, "1"), - common.NewFolderTuple("user:6", "read", "1"), - common.NewNamespaceResourceTuple("user:7", "read", folderGroup, folderResource), + common.NewResourceTuple("user:1", common.RelationGet, dashboardGroup, dashboardResource, "1"), + common.NewResourceTuple("user:1", common.RelationUpdate, dashboardGroup, dashboardResource, "1"), + common.NewGroupResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource), + common.NewGroupResourceTuple("user:2", common.RelationUpdate, dashboardGroup, dashboardResource), + common.NewResourceTuple("user:3", common.RelationSetView, dashboardGroup, dashboardResource, "1"), + common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "1"), + common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "3"), + common.NewFolderResourceTuple("user:5", common.RelationSetEdit, dashboardGroup, dashboardResource, "1"), + common.NewFolderTuple("user:6", common.RelationGet, "1"), + common.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource), common.NewFolderParentTuple("5", "4"), common.NewFolderParentTuple("6", "5"), - common.NewFolderResourceTuple("user:8", "edit", dashboardGroup, dashboardResource, "5"), + common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "5"), common.NewFolderResourceTuple("user:9", "create", dashboardGroup, dashboardResource, "5"), }, }, diff --git a/pkg/services/authz/zanzana/translations.go b/pkg/services/authz/zanzana/translations.go index 8432614c7db..79e103cb5ee 100644 --- a/pkg/services/authz/zanzana/translations.go +++ b/pkg/services/authz/zanzana/translations.go @@ -56,18 +56,14 @@ var resourceTranslations = map[string]resourceTranslation{ group: folderGroup, resource: folderResource, mapping: map[string]actionMappig{ - "folders:read": newMapping(RelationRead), - "folders:write": newMapping(RelationWrite), - "folders:create": newMapping(RelationCreate), - "folders:delete": newMapping(RelationDelete), - "folders.permissions:read": newMapping(RelationPermissionsRead), - "folders.permissions:write": newMapping(RelationPermissionsWrite), - "dashboards:read": newScopedMapping(RelationRead, dashboardGroup, dashboardResource), - "dashboards:write": newScopedMapping(RelationWrite, dashboardGroup, dashboardResource), - "dashboards:create": newScopedMapping(RelationCreate, dashboardGroup, dashboardResource), - "dashboards:delete": newScopedMapping(RelationDelete, dashboardGroup, dashboardResource), - "dashboards.permissions:read": newScopedMapping(RelationPermissionsRead, dashboardGroup, dashboardResource), - "dashboards.permissions:write": newScopedMapping(RelationPermissionsWrite, dashboardGroup, dashboardResource), + "folders:read": newMapping(RelationGet), + "folders:write": newMapping(RelationUpdate), + "folders:create": newMapping(RelationCreate), + "folders:delete": newMapping(RelationDelete), + "dashboards:read": newScopedMapping(RelationGet, dashboardGroup, dashboardResource), + "dashboards:write": newScopedMapping(RelationUpdate, dashboardGroup, dashboardResource), + "dashboards:create": newScopedMapping(RelationCreate, dashboardGroup, dashboardResource), + "dashboards:delete": newScopedMapping(RelationDelete, dashboardGroup, dashboardResource), }, }, KindDashboards: { @@ -75,12 +71,10 @@ var resourceTranslations = map[string]resourceTranslation{ group: dashboardGroup, resource: dashboardResource, mapping: map[string]actionMappig{ - "dashboards:read": newMapping(RelationRead), - "dashboards:write": newMapping(RelationWrite), - "dashboards:create": newMapping(RelationCreate), - "dashboards:delete": newMapping(RelationDelete), - "dashboards.permissions:read": newMapping(RelationPermissionsRead), - "dashboards.permissions:write": newMapping(RelationPermissionsWrite), + "dashboards:read": newMapping(RelationGet), + "dashboards:write": newMapping(RelationUpdate), + "dashboards:create": newMapping(RelationCreate), + "dashboards:delete": newMapping(RelationDelete), }, }, } diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 512f1e6a07f..5160a3f83d9 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -18,7 +18,7 @@ const ( TypeRole = common.TypeRole TypeFolder = common.TypeFolder TypeResource = common.TypeResource - TypeNamespace = common.TypeNamespace + TypeNamespace = common.TypeGroupResouce ) const ( @@ -31,28 +31,25 @@ const ( RelationSetEdit = common.RelationSetEdit RelationSetAdmin = common.RelationSetAdmin - RelationRead = common.RelationRead - RelationWrite = common.RelationWrite - RelationCreate = common.RelationCreate - RelationDelete = common.RelationDelete - RelationPermissionsRead = common.RelationPermissionsRead - RelationPermissionsWrite = common.RelationPermissionsWrite + RelationGet = common.RelationGet + RelationUpdate = common.RelationUpdate + RelationCreate = common.RelationCreate + RelationDelete = common.RelationDelete RelationFolderResourceSetView = common.RelationFolderResourceSetView RelationFolderResourceSetEdit = common.RelationFolderResourceSetEdit RelationFolderResourceSetAdmin = common.RelationFolderResourceSetAdmin - RelationFolderResourceRead = common.RelationFolderResourceRead - RelationFolderResourceWrite = common.RelationFolderResourceWrite - RelationFolderResourceCreate = common.RelationFolderResourceCreate - RelationFolderResourceDelete = common.RelationFolderResourceDelete - RelationFolderResourcePermissionsRead = common.RelationFolderResourcePermissionsRead - RelationFolderResourcePermissionsWrite = common.RelationFolderResourcePermissionsWrite + RelationFolderResourceRead = common.RelationFolderResourceGet + RelationFolderResourceWrite = common.RelationFolderResourceUpdate + RelationFolderResourceCreate = common.RelationFolderResourceCreate + RelationFolderResourceDelete = common.RelationFolderResourceDelete ) var ( - FolderRelations = common.FolderRelations - ResourceRelations = common.ResourceRelations + RelationsFolder = common.RelationsFolder + RelationsFolderResource = common.RelationsFolder + RelationsResouce = common.RelationsResource ) const ( @@ -98,7 +95,7 @@ func TranslateToResourceTuple(subject string, action, kind, name string) (*openf } if name == "*" { - return common.NewNamespaceResourceTuple(subject, m.relation, translation.group, translation.resource), true + return common.NewGroupResourceTuple(subject, m.relation, translation.group, translation.resource), true } if translation.typ == TypeResource { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 451bc5c2e82..730ad3741d0 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1248,7 +1248,7 @@ var ( Stage: FeatureStageGeneralAvailability, Owner: grafanaSharingSquad, FrontendOnly: true, - Expression: "false", // disabled by default + Expression: "true", // enabled by default }, { Name: "alertingListViewV2", @@ -1436,8 +1436,9 @@ var ( { Name: "newFiltersUI", Description: "Enables new combobox style UI for the Ad hoc filters variable in scenes architecture", - Stage: FeatureStageExperimental, + Stage: FeatureStageGeneralAvailability, Owner: grafanaDashboardsSquad, + Expression: "true", // enabled by default }, { Name: "lokiSendDashboardPanelNames", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 4e5cc8adc4b..78efd1d260b 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -188,7 +188,7 @@ cloudwatchMetricInsightsCrossAccount,GA,@grafana/aws-datasources,false,false,tru prometheusAzureOverrideAudience,deprecated,@grafana/partner-datasources,false,false,false alertingFilterV2,experimental,@grafana/alerting-squad,false,false,false dataplaneAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false -newFiltersUI,experimental,@grafana/dashboards-squad,false,false,false +newFiltersUI,GA,@grafana/dashboards-squad,false,false,false lokiSendDashboardPanelNames,experimental,@grafana/observability-logs,false,false,false alertingPrometheusRulesPrimary,experimental,@grafana/alerting-squad,false,false,true singleTopNav,GA,@grafana/grafana-frontend-platform,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index d62159c456e..324b18dee2f 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2327,10 +2327,10 @@ { "metadata": { "name": "newDashboardSharingComponent", - "resourceVersion": "1726241874335", + "resourceVersion": "1733231733564", "creationTimestamp": "2024-05-03T15:02:18Z", "annotations": { - "grafana.app/updatedTimestamp": "2024-09-13 15:37:54.335099 +0000 UTC" + "grafana.app/updatedTimestamp": "2024-12-03 13:15:33.564083 +0000 UTC" } }, "spec": { @@ -2338,7 +2338,7 @@ "stage": "GA", "codeowner": "@grafana/sharing-squad", "frontend": true, - "expression": "false" + "expression": "true" } }, { @@ -2358,13 +2358,17 @@ { "metadata": { "name": "newFiltersUI", - "resourceVersion": "1724228641625", - "creationTimestamp": "2024-08-30T12:48:13Z" + "resourceVersion": "1733391902652", + "creationTimestamp": "2024-08-30T12:48:13Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-12-05 09:45:02.652078 +0000 UTC" + } }, "spec": { "description": "Enables new combobox style UI for the Ad hoc filters variable in scenes architecture", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad" + "stage": "GA", + "codeowner": "@grafana/dashboards-squad", + "expression": "true" } }, { diff --git a/pkg/services/sqlstore/sqlutil/sqlutil.go b/pkg/services/sqlstore/sqlutil/sqlutil.go index 38bf06b2476..1a2008a1271 100644 --- a/pkg/services/sqlstore/sqlutil/sqlutil.go +++ b/pkg/services/sqlstore/sqlutil/sqlutil.go @@ -110,7 +110,8 @@ func sqLite3TestDB() (*TestDB, error) { ret.ConnStr = "file:" + sqliteDb + "?cache=private&mode=rwc" if os.Getenv("SQLITE_JOURNAL_MODE") != "false" { - ret.ConnStr += "&_journal_mode=WAL" + // For tests, set sync=OFF for faster commits. Reference: https://www.sqlite.org/pragma.html#pragma_synchronous. + ret.ConnStr += "&_journal_mode=WAL&_synchronous=OFF" } ret.Path = sqliteDb diff --git a/pkg/storage/unified/resource/search.go b/pkg/storage/unified/resource/search.go index 9ea3a321a36..341470812a7 100644 --- a/pkg/storage/unified/resource/search.go +++ b/pkg/storage/unified/resource/search.go @@ -8,7 +8,6 @@ import ( "sync" "time" - "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/hashicorp/golang-lru/v2/expirable" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -171,7 +170,7 @@ func (s *searchSupport) Search(ctx context.Context, req *ResourceSearchRequest) // init is called during startup. any failure will block startup and continued execution func (s *searchSupport) init(ctx context.Context) error { - _, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Init") + ctx, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Init") defer span.End() start := time.Now().Unix() @@ -214,6 +213,7 @@ func (s *searchSupport) init(ctx context.Context) error { }() end := time.Now().Unix() + s.log.Info("search index initialized", "duration_secs", end-start, "total_docs", s.search.TotalDocs()) if IndexMetrics != nil { IndexMetrics.IndexCreationTime.WithLabelValues().Observe(float64(end - start)) } @@ -277,7 +277,7 @@ func (s *searchSupport) handleEvent(ctx context.Context, evt *WrittenEvent) { // record latency from when event was created to when it was indexed latencySeconds := float64(time.Now().UnixMicro()-evt.ResourceVersion) / 1e6 if latencySeconds > 5 { - logger.Warn("high index latency", "latency", latencySeconds) + s.log.Warn("high index latency", "latency", latencySeconds) } if IndexMetrics != nil { IndexMetrics.IndexLatency.WithLabelValues(evt.Key.Resource).Observe(latencySeconds) @@ -307,7 +307,7 @@ func (s *searchSupport) getOrCreateIndex(ctx context.Context, key NamespacedReso } func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size int64, rv int64) (ResourceIndex, int64, error) { - _, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Build") + ctx, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Build") defer span.End() builder, err := s.builders.get(ctx, nsr) diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 8339deefbc8..8d171135663 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -255,6 +255,12 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { } } + err := s.Init(ctx) + if err != nil { + s.log.Error("error initializing resource server", "error", err) + return nil, err + } + return s, nil } @@ -294,16 +300,16 @@ func (s *server) Init(ctx context.Context) error { } } - // Start watching for changes - if s.initErr == nil { - s.initErr = s.initWatcher() - } - // initialize the search index if s.initErr == nil && s.search != nil { s.initErr = s.search.init(ctx) } + // Start watching for changes + if s.initErr == nil { + s.initErr = s.initWatcher() + } + if s.initErr != nil { s.log.Error("error initializing resource server", "error", s.initErr) } @@ -446,10 +452,6 @@ func (s *server) Create(ctx context.Context, req *CreateRequest) (*CreateRespons ctx, span := s.tracer.Start(ctx, "storage_server.Create") defer span.End() - if err := s.Init(ctx); err != nil { - return nil, err - } - rsp := &CreateResponse{} user, ok := claims.From(ctx) if !ok || user == nil { @@ -488,10 +490,6 @@ func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateRespons ctx, span := s.tracer.Start(ctx, "storage_server.Update") defer span.End() - if err := s.Init(ctx); err != nil { - return nil, err - } - rsp := &UpdateResponse{} user, ok := claims.From(ctx) if !ok || user == nil { @@ -542,10 +540,6 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons ctx, span := s.tracer.Start(ctx, "storage_server.Delete") defer span.End() - if err := s.Init(ctx); err != nil { - return nil, err - } - rsp := &DeleteResponse{} if req.ResourceVersion < 0 { return nil, apierrors.NewBadRequest("update must include the previous version") @@ -634,9 +628,6 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons } func (s *server) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) { - if err := s.Init(ctx); err != nil { - return nil, err - } user, ok := claims.From(ctx) if !ok || user == nil { return &ReadResponse{ @@ -693,9 +684,6 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err }}, nil } - if err := s.Init(ctx); err != nil { - return nil, err - } if req.Limit < 1 { req.Limit = 50 // default max 50 items in a page } @@ -786,10 +774,6 @@ func (s *server) initWatcher() error { func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error { ctx := srv.Context() - if err := s.Init(ctx); err != nil { - return err - } - user, ok := claims.From(ctx) if !ok || user == nil { return apierrors.NewUnauthorized("no user found in context") @@ -930,9 +914,6 @@ func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error { } func (s *server) Search(ctx context.Context, req *ResourceSearchRequest) (*ResourceSearchResponse, error) { - if err := s.Init(ctx); err != nil { - return nil, err - } if s.search == nil { return nil, fmt.Errorf("search index not configured") } @@ -941,25 +922,16 @@ func (s *server) Search(ctx context.Context, req *ResourceSearchRequest) (*Resou // History implements ResourceServer. func (s *server) History(ctx context.Context, req *HistoryRequest) (*HistoryResponse, error) { - if err := s.Init(ctx); err != nil { - return nil, err - } return s.search.History(ctx, req) } // Origin implements ResourceServer. func (s *server) Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error) { - if err := s.Init(ctx); err != nil { - return nil, err - } return s.search.Origin(ctx, req) } // IsHealthy implements ResourceServer. func (s *server) IsHealthy(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) { - if err := s.Init(ctx); err != nil { - return nil, err - } return s.diagnostics.IsHealthy(ctx, req) } @@ -971,9 +943,6 @@ func (s *server) PutBlob(ctx context.Context, req *PutBlobRequest) (*PutBlobResp Code: http.StatusNotImplemented, }}, nil } - if err := s.Init(ctx); err != nil { - return nil, err - } rsp, err := s.blob.PutResourceBlob(ctx, req) if err != nil { @@ -1016,10 +985,6 @@ func (s *server) GetBlob(ctx context.Context, req *GetBlobRequest) (*GetBlobResp }}, nil } - if err := s.Init(ctx); err != nil { - return nil, err - } - // The linked blob is stored in the resource metadata attributes obj, status := s.getPartialObject(ctx, req.Resource, req.ResourceVersion) if status != nil { diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index b8047142fe6..932c69e3128 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -85,9 +85,6 @@ func (b *bleveBackend) BuildIndex(ctx context.Context, // The builder will write all documents before returning builder func(index resource.ResourceIndex) (int64, error), ) (resource.ResourceIndex, error) { - b.cacheMu.Lock() - defer b.cacheMu.Unlock() - _, span := b.tracer.Start(ctx, tracingPrexfixBleve+"BuildIndex") defer span.End() @@ -99,9 +96,9 @@ func (b *bleveBackend) BuildIndex(ctx context.Context, if size > b.opts.FileThreshold { dir := filepath.Join(b.opts.Root, key.Namespace, fmt.Sprintf("%s.%s", key.Resource, key.Group)) index, err = bleve.New(dir, mapper) - if err == nil { - b.log.Info("TODO, check last RV so we can see if the numbers have changed", "dir", dir) - } + + // TODO, check last RV so we can see if the numbers have changed + resource.IndexMetrics.IndexTenants.WithLabelValues(key.Namespace, "file").Inc() } else { index, err = bleve.NewMemOnly(mapper) @@ -137,7 +134,9 @@ func (b *bleveBackend) BuildIndex(ctx context.Context, return nil, err } + b.cacheMu.Lock() b.cache[key] = idx + b.cacheMu.Unlock() return idx, nil } diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index ae207870164..965930dcf54 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -123,7 +123,7 @@ func (b *backend) Stop(_ context.Context) error { // GetResourceStats implements Backend. func (b *backend) GetResourceStats(ctx context.Context, namespace string, minCount int) ([]resource.ResourceStats, error) { - _, span := b.tracer.Start(ctx, tracePrefix+".GetResourceStats") + ctx, span := b.tracer.Start(ctx, tracePrefix+".GetResourceStats") defer span.End() req := &sqlStatsRequest{ diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 0b675139d31..60f43905176 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -461,9 +461,14 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) { } } } - logSection, err := getOrCreateSection("database") + + dbSection, err := getOrCreateSection("database") + require.NoError(t, err) + _, err = dbSection.NewKey("query_retries", fmt.Sprintf("%d", queryRetries)) + require.NoError(t, err) + _, err = dbSection.NewKey("max_open_conn", "2") require.NoError(t, err) - _, err = logSection.NewKey("query_retries", fmt.Sprintf("%d", queryRetries)) + _, err = dbSection.NewKey("max_idle_conn", "2") require.NoError(t, err) cfgPath := filepath.Join(cfgDir, "test.ini") diff --git a/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go b/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go index 96f3f53b952..c761b271f88 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go @@ -121,8 +121,8 @@ func writeErrorResponse(rw http.ResponseWriter, code int, msg string) { errorBody := map[string]string{ "error": msg, } - json, _ := json.Marshal(errorBody) - _, err := rw.Write(json) + jsonRes, _ := json.Marshal(errorBody) + _, err := rw.Write(jsonRes) if err != nil { backend.Logger.Error("Unable to write HTTP response", "error", err) } diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go index 6dedac9bdf8..a8584dfc59a 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go @@ -16,7 +16,7 @@ import ( func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { err := e.Ping() if err != nil { - logCheckHealthError(ctx, e.dsInfo, err, e.log) + logCheckHealthError(ctx, e.dsInfo, err) if strings.EqualFold(req.PluginContext.User.Role, "Admin") { return ErrToHealthCheckResult(err) } @@ -73,7 +73,8 @@ func ErrToHealthCheckResult(err error) (*backend.CheckHealthResult, error) { return res, nil } -func logCheckHealthError(_ context.Context, dsInfo DataSourceInfo, err error, logger log.Logger) { +func logCheckHealthError(ctx context.Context, dsInfo DataSourceInfo, err error) { + logger := log.DefaultLogger.FromContext(ctx) configSummary := map[string]any{ "config_url_length": len(dsInfo.URL), "config_user_length": len(dsInfo.User), @@ -104,8 +105,8 @@ func logCheckHealthError(_ context.Context, dsInfo DataSourceInfo, err error, lo } configSummaryJson, marshalError := json.Marshal(configSummary) if marshalError != nil { - logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error", "plugin_id", "grafana-postgresql-datasource") + logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error") return } - logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error_detailed", "plugin_id", "grafana-postgresql-datasource", "details", string(configSummaryJson)) + logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error_detailed", "details", string(configSummaryJson)) } diff --git a/pkg/tsdb/mssql/kerberos/kerberos.go b/pkg/tsdb/mssql/kerberos/kerberos.go index 207f324672a..abd9303231b 100644 --- a/pkg/tsdb/mssql/kerberos/kerberos.go +++ b/pkg/tsdb/mssql/kerberos/kerberos.go @@ -73,8 +73,6 @@ func Krb5ParseAuthCredentials(host string, port string, db string, user string, krb5DriverParams += "krb5-dnslookupkdc=" + kerberosAuth.EnableDNSLookupKDC + ";" } - logger.Info(fmt.Sprintf("final krb connstr: %s", krb5DriverParams)) - return krb5DriverParams } diff --git a/pkg/tsdb/mssql/sqleng/handler_checkhealth.go b/pkg/tsdb/mssql/sqleng/handler_checkhealth.go new file mode 100644 index 00000000000..ade94fe7349 --- /dev/null +++ b/pkg/tsdb/mssql/sqleng/handler_checkhealth.go @@ -0,0 +1,93 @@ +package sqleng + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "strings" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + if err := e.db.Ping(); err != nil { + logCheckHealthError(ctx, e.dsInfo, err) + if strings.EqualFold(req.PluginContext.User.Role, "Admin") { + return ErrToHealthCheckResult(err) + } + return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: e.TransformQueryError(e.log, err).Error()}, nil + } + return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "Database Connection OK"}, nil +} + +// ErrToHealthCheckResult converts error into user friendly health check message +// This should be called with non nil error. If the err parameter is empty, we will send Internal Server Error +func ErrToHealthCheckResult(err error) (*backend.CheckHealthResult, error) { + if err == nil { + return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: "Internal Server Error"}, nil + } + res := &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: err.Error()} + details := map[string]string{ + "verboseMessage": err.Error(), + "errorDetailsLink": "https://grafana.com/docs/grafana/latest/datasources/mssql", + } + var opErr *net.OpError + if errors.As(err, &opErr) { + res.Message = "Network error: Failed to connect to the server" + if opErr != nil && opErr.Err != nil { + res.Message += fmt.Sprintf(". Error message: %s", opErr.Err.Error()) + } + } + if strings.HasPrefix(err.Error(), "mssql: ") { + res.Message = "Database error: Failed to connect to the mssql server" + if unwrappedErr := errors.Unwrap(err); unwrappedErr != nil { + details["verboseMessage"] = unwrappedErr.Error() + } + } + detailBytes, marshalErr := json.Marshal(details) + if marshalErr != nil { + return res, nil + } + res.JSONDetails = detailBytes + return res, nil +} + +func logCheckHealthError(ctx context.Context, dsInfo DataSourceInfo, err error) { + logger := log.DefaultLogger.FromContext(ctx) + configSummary := map[string]any{ + "config_url_length": len(dsInfo.URL), + "config_user_length": len(dsInfo.User), + "config_database_length": len(dsInfo.Database), + "config_json_data_database_length": len(dsInfo.JsonData.Database), + "config_max_open_conns": dsInfo.JsonData.MaxOpenConns, + "config_max_idle_conns": dsInfo.JsonData.MaxIdleConns, + "config_conn_max_life_time": dsInfo.JsonData.ConnMaxLifetime, + "config_conn_timeout": dsInfo.JsonData.ConnectionTimeout, + "config_ssl_mode": dsInfo.JsonData.Mode, + "config_tls_configuration_method": dsInfo.JsonData.ConfigurationMethod, + "config_tls_skip_verify": dsInfo.JsonData.TlsSkipVerify, + "config_timezone": dsInfo.JsonData.Timezone, + "config_time_interval": dsInfo.JsonData.TimeInterval, + "config_enable_secure_proxy": dsInfo.JsonData.SecureDSProxy, + "config_allow_clear_text_passwords": dsInfo.JsonData.AllowCleartextPasswords, + "config_authentication_type": dsInfo.JsonData.AuthenticationType, + "config_ssl_root_cert_file_length": len(dsInfo.JsonData.RootCertFile), + "config_ssl_cert_file_length": len(dsInfo.JsonData.CertFile), + "config_ssl_key_file_length": len(dsInfo.JsonData.CertKeyFile), + "config_encrypt_length": len(dsInfo.JsonData.Encrypt), + "config_server_name_length": len(dsInfo.JsonData.Servername), + "config_password_length": len(dsInfo.DecryptedSecureJSONData["password"]), + "config_tls_ca_cert_length": len(dsInfo.DecryptedSecureJSONData["tlsCACert"]), + "config_tls_client_cert_length": len(dsInfo.DecryptedSecureJSONData["tlsClientCert"]), + "config_tls_client_key_length": len(dsInfo.DecryptedSecureJSONData["tlsClientKey"]), + } + configSummaryJson, marshalError := json.Marshal(configSummary) + if marshalError != nil { + logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error") + return + } + logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error_detailed", "details", string(configSummaryJson)) +} diff --git a/pkg/tsdb/mssql/sqleng/handler_checkhealth_test.go b/pkg/tsdb/mssql/sqleng/handler_checkhealth_test.go new file mode 100644 index 00000000000..de41ffe1c58 --- /dev/null +++ b/pkg/tsdb/mssql/sqleng/handler_checkhealth_test.go @@ -0,0 +1,60 @@ +package sqleng + +import ( + "errors" + "net" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + mssql "github.com/microsoft/go-mssqldb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestErrToHealthCheckResult(t *testing.T) { + tests := []struct { + name string + err error + want *backend.CheckHealthResult + }{ + { + name: "without error", + want: &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: "Internal Server Error"}, + }, + { + name: "network error", + err: errors.Join(errors.New("foo"), &net.OpError{Op: "read", Net: "tcp", Err: errors.New("some op")}), + want: &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: "Network error: Failed to connect to the server. Error message: some op", + JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/mssql","verboseMessage":"foo\nread tcp: some op"}`), + }, + }, + { + name: "db error", + err: errors.Join(errors.New("foo"), &mssql.Error{Message: "error foo occurred in mssql server"}), + want: &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: "foo\nmssql: error foo occurred in mssql server", + JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/mssql","verboseMessage":"foo\nmssql: error foo occurred in mssql server"}`), + }, + }, + { + name: "regular error", + err: errors.New("internal server error"), + want: &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: "internal server error", + JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/mssql","verboseMessage":"internal server error"}`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ErrToHealthCheckResult(tt.err) + require.Nil(t, err) + assert.Equal(t, string(tt.want.JSONDetails), string(got.JSONDetails)) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/tsdb/mssql/sqleng/sql_engine.go b/pkg/tsdb/mssql/sqleng/sql_engine.go index 153c5351258..27e34e52c11 100644 --- a/pkg/tsdb/mssql/sqleng/sql_engine.go +++ b/pkg/tsdb/mssql/sqleng/sql_engine.go @@ -152,15 +152,6 @@ func (e *DataSourceHandler) Dispose() { e.log.Debug("DB disposed") } -func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { - err := e.db.Ping() - - if err != nil { - return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: e.TransformQueryError(e.log, err).Error()}, nil - } - return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "Database Connection OK"}, nil -} - func (e *DataSourceHandler) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { result := backend.NewQueryDataResponse() ch := make(chan DBDataResponse, len(req.Queries)) diff --git a/pkg/tsdb/mysql/sqleng/handler_checkhealth.go b/pkg/tsdb/mysql/sqleng/handler_checkhealth.go index d224bb9d7ee..fea2b0b8f8f 100644 --- a/pkg/tsdb/mysql/sqleng/handler_checkhealth.go +++ b/pkg/tsdb/mysql/sqleng/handler_checkhealth.go @@ -16,7 +16,7 @@ import ( func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { err := e.db.Ping() if err != nil { - logCheckHealthError(ctx, e.dsInfo, err, e.log) + logCheckHealthError(ctx, e.dsInfo, err) if strings.EqualFold(req.PluginContext.User.Role, "Admin") { return ErrToHealthCheckResult(err) } @@ -63,7 +63,8 @@ func ErrToHealthCheckResult(err error) (*backend.CheckHealthResult, error) { return res, nil } -func logCheckHealthError(_ context.Context, dsInfo DataSourceInfo, err error, logger log.Logger) { +func logCheckHealthError(ctx context.Context, dsInfo DataSourceInfo, err error) { + logger := log.DefaultLogger.FromContext(ctx) configSummary := map[string]any{ "config_url_length": len(dsInfo.URL), "config_user_length": len(dsInfo.User), @@ -94,8 +95,8 @@ func logCheckHealthError(_ context.Context, dsInfo DataSourceInfo, err error, lo } configSummaryJson, marshalError := json.Marshal(configSummary) if marshalError != nil { - logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error", "plugin_id", "mysql") + logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error") return } - logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error_detailed", "plugin_id", "mysql", "details", string(configSummaryJson)) + logger.Error("Check health failed", "error", err, "message_type", "ds_config_health_check_error_detailed", "details", string(configSummaryJson)) } diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index d2c775085d5..cdd858d9dcc 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -2211,6 +2211,113 @@ } } }, + "/scim/users/": { + "get": { + "tags": [ + "users", + "enterprise" + ], + "summary": "Fetches all users in UserSchema format.", + "operationId": "getUsers", + "responses": { + "200": { + "$ref": "#/responses/getUsers" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "tags": [ + "users", + "enterprise" + ], + "summary": "Creates user.", + "operationId": "createUser", + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "409": { + "$ref": "#/responses/conflictError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "delete": { + "tags": [ + "user", + "enterprise" + ], + "summary": "Deletes user.", + "operationId": "deleteUser", + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/scim/users/:id": { + "get": { + "tags": [ + "user", + "enterprise" + ], + "summary": "Gets user by id.", + "operationId": "getUser", + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/teams/{teamId}/groups": { "get": { "tags": [ diff --git a/public/api-merged.json b/public/api-merged.json index 1978029ed6c..e9b63723b69 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -8609,7 +8609,8 @@ "/scim/users/": { "get": { "tags": [ - "users" + "users", + "enterprise" ], "summary": "Fetches all users in UserSchema format.", "operationId": "getUsers", @@ -8633,7 +8634,8 @@ }, "post": { "tags": [ - "users" + "users", + "enterprise" ], "summary": "Creates user.", "operationId": "createUser", @@ -8660,7 +8662,8 @@ }, "delete": { "tags": [ - "user" + "user", + "enterprise" ], "summary": "Deletes user.", "operationId": "deleteUser", @@ -8686,7 +8689,8 @@ "/scim/users/:id": { "get": { "tags": [ - "user" + "user", + "enterprise" ], "summary": "Gets user by id.", "operationId": "getUser", diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx index 35a90efe365..2b4693d3a38 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx +++ b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx @@ -1,14 +1,16 @@ import { css } from '@emotion/css'; -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { GrafanaTheme2, VariableHide } from '@grafana/data'; import { CustomVariable, EmbeddedScene, PanelBuilders, + SceneComponentProps, SceneControlsSpacer, SceneFlexItem, SceneFlexLayout, + SceneObjectBase, SceneQueryRunner, SceneReactObject, SceneRefreshPicker, @@ -16,7 +18,9 @@ import { SceneTimeRange, SceneVariableSet, TextBoxVariable, + VariableDependencyConfig, VariableValueSelectors, + sceneGraph, useUrlSync, } from '@grafana/scenes'; import { GraphDrawStyle, VisibilityMode } from '@grafana/schema/dist/esm/index'; @@ -36,14 +40,13 @@ import { import { Trans } from 'app/core/internationalization'; import { LogMessages, logInfo } from '../../../Analytics'; -import { DataSourceInformation } from '../../../home/Insights'; import { alertStateHistoryDatasource, useRegisterHistoryRuntimeDataSource } from './CentralHistoryRuntimeDataSource'; import { HistoryEventsListObject } from './EventListSceneObject'; -export const LABELS_FILTER = 'labelsFilter'; -export const STATE_FILTER_TO = 'stateFilterTo'; -export const STATE_FILTER_FROM = 'stateFilterFrom'; +export const LABELS_FILTER = 'LABELS_FILTER'; +export const STATE_FILTER_TO = 'STATE_FILTER_TO'; +export const STATE_FILTER_FROM = 'STATE_FILTER_FROM'; /** * * This scene shows the history of the alert state changes. @@ -67,74 +70,72 @@ export const CentralAlertHistoryScene = () => { logInfo(LogMessages.loadedCentralAlertStateHistory); }, []); - // create the variables for the filters - // textbox variable for filtering by labels - const labelsFilterVariable = new TextBoxVariable({ - name: LABELS_FILTER, - label: 'Labels: ', - }); - //custom variable for filtering by the current state - const transitionsToFilterVariable = new CustomVariable({ - name: STATE_FILTER_TO, - value: StateFilterValues.all, - label: 'End state:', - hide: VariableHide.dontHide, - query: `All : ${StateFilterValues.all}, To Firing : ${StateFilterValues.firing},To Normal : ${StateFilterValues.normal},To Pending : ${StateFilterValues.pending}`, - }); - //custom variable for filtering by the previous state - const transitionsFromFilterVariable = new CustomVariable({ - name: STATE_FILTER_FROM, - value: StateFilterValues.all, - label: 'Start state:', - hide: VariableHide.dontHide, - query: `All : ${StateFilterValues.all}, From Firing : ${StateFilterValues.firing},From Normal : ${StateFilterValues.normal},From Pending : ${StateFilterValues.pending}`, - }); - useRegisterHistoryRuntimeDataSource(); // register the runtime datasource for the history api. - const scene = new EmbeddedScene({ - controls: [ - new SceneReactObject({ - component: LabelFilter, - }), - new SceneReactObject({ - component: FilterInfo, - }), - new VariableValueSelectors({}), - new SceneReactObject({ - component: ClearFilterButton, - props: { - labelsFilterVariable, - transitionsToFilterVariable, - transitionsFromFilterVariable, - }, - }), - new SceneControlsSpacer(), - new SceneTimePicker({}), - new SceneRefreshPicker({}), - ], - // use default time range as from 1 hour ago to now, as the limit of the history api is 5000 events, - // and using a wider time range might lead to showing gaps in the events list and the chart. - $timeRange: new SceneTimeRange({ - from: 'now-1h', - to: 'now', - }), - $variables: new SceneVariableSet({ - variables: [labelsFilterVariable, transitionsFromFilterVariable, transitionsToFilterVariable], - }), - body: new SceneFlexLayout({ - direction: 'column', - children: [ - new SceneFlexItem({ - ySizing: 'content', - body: getEventsSceneObject(alertStateHistoryDatasource), + const scene = useMemo(() => { + // create the variables for the filters + // textbox variable for filtering by labels + const labelsFilterVariable = new TextBoxVariable({ + name: LABELS_FILTER, + label: 'Labels: ', + }); + + //custom variable for filtering by the current state + const transitionsToFilterVariable = new CustomVariable({ + name: STATE_FILTER_TO, + value: StateFilterValues.all, + label: 'End state:', + hide: VariableHide.dontHide, + query: `All : ${StateFilterValues.all}, To Firing : ${StateFilterValues.firing},To Normal : ${StateFilterValues.normal},To Pending : ${StateFilterValues.pending}`, + }); + + //custom variable for filtering by the previous state + const transitionsFromFilterVariable = new CustomVariable({ + name: STATE_FILTER_FROM, + value: StateFilterValues.all, + label: 'Start state:', + hide: VariableHide.dontHide, + query: `All : ${StateFilterValues.all}, From Firing : ${StateFilterValues.firing},From Normal : ${StateFilterValues.normal},From Pending : ${StateFilterValues.pending}`, + }); + + return new EmbeddedScene({ + controls: [ + new SceneReactObject({ + component: LabelFilter, }), - new SceneFlexItem({ - body: new HistoryEventsListObject(), + new SceneReactObject({ + component: FilterInfo, }), + new VariableValueSelectors({}), + new ClearFilterButtonScenesObject({}), + new SceneControlsSpacer(), + new SceneTimePicker({}), + new SceneRefreshPicker({}), ], - }), - }); + // use default time range as from 1 hour ago to now, as the limit of the history api is 5000 events, + // and using a wider time range might lead to showing gaps in the events list and the chart. + $timeRange: new SceneTimeRange({ + from: 'now-1h', + to: 'now', + }), + $variables: new SceneVariableSet({ + variables: [labelsFilterVariable, transitionsFromFilterVariable, transitionsToFilterVariable], + }), + body: new SceneFlexLayout({ + direction: 'column', + children: [ + new SceneFlexItem({ + ySizing: 'content', + body: getEventsSceneObject(), + }), + new SceneFlexItem({ + body: new HistoryEventsListObject({}), + }), + ], + }), + }); + }, []); + // we need to call this to sync the url with the scene state const isUrlSyncInitialized = useUrlSync(scene); @@ -147,22 +148,11 @@ export const CentralAlertHistoryScene = () => { /** * Creates a SceneFlexItem with a timeseries panel that shows the events. * The query uses a runtime datasource that fetches the events from the history api. - * @param alertStateHistoryDataSource the datasource information for the runtime datasource */ -function getEventsSceneObject(alertStateHistoryDataSource: DataSourceInformation) { - return new EmbeddedScene({ - controls: [], - body: new SceneFlexLayout({ - direction: 'column', - children: [ - new SceneFlexItem({ - ySizing: 'content', - body: new SceneFlexLayout({ - children: [getEventsScenesFlexItem(alertStateHistoryDataSource)], - }), - }), - ], - }), +function getEventsSceneObject() { + return new SceneFlexLayout({ + direction: 'column', + children: [getEventsScenesFlexItem()], }); } @@ -171,15 +161,15 @@ function getEventsSceneObject(alertStateHistoryDataSource: DataSourceInformation * @param datasource the datasource information for the runtime datasource * @returns the SceneQueryRunner */ -function getSceneQuery(datasource: DataSourceInformation) { +function getQueryRunnerForAlertHistoryDataSource() { const query = new SceneQueryRunner({ - datasource: datasource, + datasource: alertStateHistoryDatasource, queries: [ { refId: 'A', - expr: '', - queryType: 'range', - step: '10s', + labels: '${LABELS_FILTER}', + stateFrom: '${STATE_FILTER_FROM}', + stateTo: '${STATE_FILTER_TO}', }, ], }); @@ -189,7 +179,7 @@ function getSceneQuery(datasource: DataSourceInformation) { * This function creates a SceneFlexItem with a timeseries panel that shows the events. * The query uses a runtime datasource that fetches the events from the history api. */ -export function getEventsScenesFlexItem(datasource: DataSourceInformation) { +export function getEventsScenesFlexItem() { return new SceneFlexItem({ minHeight: 300, body: PanelBuilders.timeseries() @@ -197,7 +187,7 @@ export function getEventsScenesFlexItem(datasource: DataSourceInformation) { .setDescription( 'Each alert event represents an alert instance that changed its state at a particular point in time. The history of the data is displayed over a period of time.' ) - .setData(getSceneQuery(datasource)) + .setData(getQueryRunnerForAlertHistoryDataSource()) .setColor({ mode: 'continuous-BlPu' }) .setCustomFieldConfig('fillOpacity', 100) .setCustomFieldConfig('drawStyle', GraphDrawStyle.Bars) @@ -213,47 +203,49 @@ export function getEventsScenesFlexItem(datasource: DataSourceInformation) { .setCustomFieldConfig('scaleDistribution', { type: ScaleDistribution.Linear }) .setOption('legend', { showLegend: false, displayMode: LegendDisplayMode.Hidden }) .setOption('tooltip', { mode: TooltipDisplayMode.Single }) - .setNoValue('No events found') .build(), }); } -/* - * This component shows a button to clear the filters. - * It is shown when the filters are active. - * props: - * labelsFilterVariable: the textbox variable for filtering by labels - * transitionsToFilterVariable: the custom variable for filtering by the current state - * transitionsFromFilterVariable: the custom variable for filtering by the previous state - */ -function ClearFilterButton({ - labelsFilterVariable, - transitionsToFilterVariable, - transitionsFromFilterVariable, -}: { - labelsFilterVariable: TextBoxVariable; - transitionsToFilterVariable: CustomVariable; - transitionsFromFilterVariable: CustomVariable; -}) { - // get the current values of the filters - const valueInLabelsFilter = labelsFilterVariable.getValue(); - //todo: use parsePromQLStyleMatcherLooseSafe to validate the label filter and check the lenghtof the result - const valueInTransitionsFilter = transitionsToFilterVariable.getValue(); - const valueInTransitionsFromFilter = transitionsFromFilterVariable.getValue(); +export class ClearFilterButtonScenesObject extends SceneObjectBase { + public static Component = ClearFilterButtonObjectRenderer; + + protected _variableDependency = new VariableDependencyConfig(this, { + variableNames: [LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO], + }); +} + +export function ClearFilterButtonObjectRenderer({ model }: SceneComponentProps) { + // This make sure the component is re-rendered when the variables change + model.useState(); + + const labelsFilter = sceneGraph.interpolate(model, '${LABELS_FILTER}'); + const stateTo = sceneGraph.interpolate(model, '${STATE_FILTER_TO}'); + const stateFrom = sceneGraph.interpolate(model, '${STATE_FILTER_FROM}'); + // if no filter is active, return null - if ( - !valueInLabelsFilter && - valueInTransitionsFilter === StateFilterValues.all && - valueInTransitionsFromFilter === StateFilterValues.all - ) { + if (!labelsFilter && stateTo === StateFilterValues.all && stateFrom === StateFilterValues.all) { return null; } + const onClearFilter = () => { - labelsFilterVariable.setValue(''); - transitionsToFilterVariable.changeValueTo(StateFilterValues.all); - transitionsFromFilterVariable.changeValueTo(StateFilterValues.all); + const labelsFiltersVariable = sceneGraph.lookupVariable(LABELS_FILTER, model); + if (labelsFiltersVariable instanceof TextBoxVariable) { + labelsFiltersVariable.setValue(''); + } + + const stateToFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_TO, model); + if (stateToFilterVariable instanceof CustomVariable) { + stateToFilterVariable.changeValueTo(StateFilterValues.all); + } + + const stateFromFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_FROM, model); + if (stateFromFilterVariable instanceof CustomVariable) { + stateFromFilterVariable.changeValueTo(StateFilterValues.all); + } }; + return ( + + + + ); + } +} diff --git a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx index a75b82a2531..98c93b0635e 100644 --- a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx @@ -34,7 +34,7 @@ export function getPanelFrameOptions(panel: VizPanel): OptionsPaneCategoryDescri value: panel.state.title, popularRank: 1, render: function renderTitle() { - return ; + return ; }, addon: config.featureToggles.dashgpt && ( ; + return ; }, addon: config.featureToggles.dashgpt && ( { - panel.setState({ - displayMode: panel.state.displayMode === 'transparent' ? 'default' : 'transparent', - }); - }} - /> - ); + return ; }, }) ) @@ -116,7 +106,7 @@ function ScenePanelLinksEditor({ panelLinks }: ScenePanelLinksEditorProps) { ); } -function PanelFrameTitle({ panel }: { panel: VizPanel }) { +export function PanelFrameTitleInput({ panel }: { panel: VizPanel }) { const { title } = panel.useState(); return ( @@ -128,7 +118,7 @@ function PanelFrameTitle({ panel }: { panel: VizPanel }) { ); } -function DescriptionTextArea({ panel }: { panel: VizPanel }) { +export function PanelDescriptionTextArea({ panel }: { panel: VizPanel }) { const { description } = panel.useState(); return ( @@ -140,6 +130,22 @@ function DescriptionTextArea({ panel }: { panel: VizPanel }) { ); } +export function PanelBackgroundSwitch({ panel }: { panel: VizPanel }) { + const { displayMode } = panel.useState(); + + return ( + { + panel.setState({ + displayMode: panel.state.displayMode === 'transparent' ? 'default' : 'transparent', + }); + }} + /> + ); +} + function setPanelTitle(panel: VizPanel, title: string) { panel.setState({ title: title, hoverHeader: getUpdatedHoverHeader(title, panel.state.$timeRange) }); } diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx index 80cf335232e..f72c9d30db7 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx @@ -60,6 +60,7 @@ export function ToolbarActions({ dashboard }: Props) { const canSaveAs = contextSrv.hasEditPermissionInFolders; const toolbarActions: ToolbarAction[] = []; + const leftActions: ToolbarAction[] = []; const styles = useStyles2(getStyles); const isEditingPanel = Boolean(editPanel); const isViewingPanel = Boolean(viewPanelScene); @@ -69,7 +70,8 @@ export function ToolbarActions({ dashboard }: Props) { // Means we are not in settings view, fullscreen panel or edit panel const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel; const isEditingAndShowingDashboard = isEditing && isShowingDashboard; - const showScopesSelector = config.featureToggles.singleTopNav && config.featureToggles.scopeFilters; + const showScopesSelector = config.featureToggles.singleTopNav && config.featureToggles.scopeFilters && !isEditing; + const dashboardNewLayouts = config.featureToggles.dashboardNewLayouts; if (!isEditingPanel) { // This adds the precence indicators in enterprise @@ -151,74 +153,135 @@ export function ToolbarActions({ dashboard }: Props) { addDynamicActions(toolbarActions, dynamicDashNavActions.right, 'icon-actions'); } - toolbarActions.push({ - group: 'add-panel', - condition: isEditingAndShowingDashboard, - render: () => ( - { - setIsAddPanelMenuOpen(isOpen); - DashboardInteractions.toolbarAddClick(); - }} - overlay={() => ( - - { - const vizPanel = dashboard.onCreateNewPanel(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' }); - dashboard.setState({ editPanel: buildPanelEditScene(vizPanel, true) }); - }} - /> - { - dashboard.onShowAddLibraryPanelDrawer(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' }); - }} - /> - { - dashboard.onCreateNewRow(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' }); - }} - /> - { - dashboard.pastePanel(); - DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' }); - }} - /> - - )} - placement="bottom" - offset={[0, 6]} - > + if (dashboardNewLayouts) { + leftActions.push({ + group: 'add-panel', + condition: isEditingAndShowingDashboard, + render: () => ( - - ), - }); + ), + }); + leftActions.push({ + group: 'add-panel', + condition: isEditingAndShowingDashboard, + render: () => ( + + ), + }); + leftActions.push({ + group: 'add-panel', + condition: isEditingAndShowingDashboard, + render: () => ( + + ), + }); + } else { + toolbarActions.push({ + group: 'add-panel', + condition: isEditingAndShowingDashboard, + render: () => ( + { + setIsAddPanelMenuOpen(isOpen); + DashboardInteractions.toolbarAddClick(); + }} + overlay={() => ( + + { + const vizPanel = dashboard.onCreateNewPanel(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' }); + dashboard.setState({ editPanel: buildPanelEditScene(vizPanel, true) }); + }} + /> + { + dashboard.onShowAddLibraryPanelDrawer(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' }); + }} + /> + { + dashboard.onCreateNewRow(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' }); + }} + /> + { + dashboard.pastePanel(); + DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' }); + }} + /> + + )} + placement="bottom" + offset={[0, 6]} + > + + + ), + }); + } toolbarActions.push({ group: 'playlist-actions', @@ -595,6 +658,20 @@ export function ToolbarActions({ dashboard }: Props) { }, }); + const rigthActionsElements: React.ReactNode[] = renderActionElements(toolbarActions); + const leftActionsElements: React.ReactNode[] = renderActionElements(leftActions); + const hasActionsToLeftAndRight = showScopesSelector || leftActionsElements.length > 0; + + return ( + + {showScopesSelector && } + {leftActionsElements.length > 0 && {leftActionsElements}} + {rigthActionsElements} + + ); +} + +function renderActionElements(toolbarActions: ToolbarAction[]) { const actionElements: React.ReactNode[] = []; let lastGroup = ''; @@ -610,13 +687,7 @@ export function ToolbarActions({ dashboard }: Props) { actionElements.push(action.render()); lastGroup = action.group; } - - return ( - - {showScopesSelector && } - {actionElements} - - ); + return actionElements; } function addDynamicActions( diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index 35a59a600c8..914b852d8e6 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -9,23 +9,18 @@ import { sceneUtils, SceneComponentProps, } from '@grafana/scenes'; -import { Button } from '@grafana/ui'; import { GRID_COLUMN_COUNT } from 'app/core/constants'; -import { Trans } from 'app/core/internationalization'; -import { DashboardInteractions } from '../../utils/interactions'; import { forceRenderChildren, getPanelIdForVizPanel, NEW_PANEL_HEIGHT, NEW_PANEL_WIDTH, getVizPanelKeyForPanelId, - getDefaultVizPanel, } from '../../utils/utils'; import { RowRepeaterBehavior } from '../RowRepeaterBehavior'; -import { LayoutEditChrome } from '../layouts-shared/LayoutEditChrome'; import { RowActions } from '../row-actions/RowActions'; -import { DashboardLayoutManager, LayoutEditorProps, LayoutRegistryItem } from '../types'; +import { DashboardLayoutManager, LayoutRegistryItem } from '../types'; import { DashboardGridItem } from './DashboardGridItem'; @@ -40,6 +35,8 @@ export class DefaultGridLayoutManager extends SceneObjectBase implements DashboardLayoutManager { + public isDashboardLayoutManager: true = true; + public editModeChanged(isEditing: boolean): void { const updateResizeAndDragging = () => { this.state.grid.setState({ isDraggable: isEditing, isResizable: isEditing }); @@ -387,48 +384,7 @@ export class DefaultGridLayoutManager }); } - public renderEditor() { - return ; - } - public static Component = ({ model }: SceneComponentProps) => { - if (!config.featureToggles.dashboardNewLayouts) { - return ; - } - - return ( - - - - ); + return ; }; } - -function DefaultGridLayoutEditor({ layoutManager }: LayoutEditorProps) { - return ( - <> - - - - - ); -} diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx index d1671c3cb23..342d23b42de 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx @@ -1,5 +1,8 @@ +import { css, cx } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; import { SceneObjectState, VizPanel, SceneObjectBase, SceneObject, SceneComponentProps } from '@grafana/scenes'; -import { Switch } from '@grafana/ui'; +import { Switch, useStyles2 } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -65,7 +68,22 @@ export class ResponsiveGridItem extends SceneObjectBase public static Component = ({ model }: SceneComponentProps) => { const { body } = model.useState(); + const style = useStyles2(getStyles); + + return ( +
+ +
+ ); + }; +} - return ; +function getStyles(theme: GrafanaTheme2) { + return { + wrapper: css({ + width: '100%', + height: '100%', + position: 'relative', + }), }; } diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index 25ac031c34f..51219ae9178 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -1,12 +1,10 @@ import { SelectableValue } from '@grafana/data'; import { SceneComponentProps, SceneCSSGridLayout, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; -import { Button, Field, Select } from '@grafana/ui'; -import { Trans } from 'app/core/internationalization'; +import { Select } from '@grafana/ui'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; -import { DashboardInteractions } from '../../utils/interactions'; -import { getDefaultVizPanel, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils'; -import { LayoutEditChrome } from '../layouts-shared/LayoutEditChrome'; -import { DashboardLayoutManager, LayoutRegistryItem, LayoutEditorProps } from '../types'; +import { getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils'; +import { DashboardLayoutManager, LayoutRegistryItem } from '../types'; import { ResponsiveGridItem } from './ResponsiveGridItem'; @@ -18,6 +16,8 @@ export class ResponsiveGridLayoutManager extends SceneObjectBase implements DashboardLayoutManager { + public isDashboardLayoutManager: true = true; + public editModeChanged(isEditing: boolean): void {} public addPanel(vizPanel: VizPanel): void { @@ -72,8 +72,8 @@ export class ResponsiveGridLayoutManager return panels; } - public renderEditor() { - return ; + public getOptions(): OptionsPaneItemDescriptor[] { + return getOptions(this); } public getDescriptor(): LayoutRegistryItem { @@ -90,7 +90,13 @@ export class ResponsiveGridLayoutManager } public static createEmpty() { - return new ResponsiveGridLayoutManager({ layout: new SceneCSSGridLayout({ children: [] }) }); + return new ResponsiveGridLayoutManager({ + layout: new SceneCSSGridLayout({ + children: [], + templateColumns: 'repeat(auto-fit, minmax(400px, auto))', + autoRows: 'minmax(300px, auto)', + }), + }); } public static createFromLayout(layout: DashboardLayoutManager): ResponsiveGridLayoutManager { @@ -110,18 +116,22 @@ export class ResponsiveGridLayoutManager }); } + toSaveModel?() { + throw new Error('Method not implemented.'); + } + + activateRepeaters?(): void { + throw new Error('Method not implemented.'); + } public static Component = ({ model }: SceneComponentProps) => { - return ( - - - - ); + return ; }; } -function AutomaticGridEditor({ layoutManager }: LayoutEditorProps) { +function getOptions(layoutManager: ResponsiveGridLayoutManager): OptionsPaneItemDescriptor[] { + const options: OptionsPaneItemDescriptor[] = []; + const cssLayout = layoutManager.state.layout; - const { templateColumns, autoRows } = cssLayout.useState(); const rowOptions: Array> = []; const sizes = [100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 650]; @@ -143,39 +153,43 @@ function AutomaticGridEditor({ layoutManager }: LayoutEditorProps) => { - cssLayout.setState({ templateColumns: value.value }); - }; - - const onRowsChange = (value: SelectableValue) => { - cssLayout.setState({ autoRows: value.value }); - }; + options.push( + new OptionsPaneItemDescriptor({ + title: 'Columns', + render: () => { + const { templateColumns } = cssLayout.useState(); + return ( + - - - - { + cssLayout.setState({ autoRows: value.value }); + }} + allowCustomValue={true} + /> + ); + }, + }) ); + + return options; } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx new file mode 100644 index 00000000000..1a224fadb0c --- /dev/null +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx @@ -0,0 +1,242 @@ +import { css, cx } from '@emotion/css'; +import { useMemo, useRef } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { SceneObjectState, SceneObjectBase, SceneComponentProps, sceneGraph } from '@grafana/scenes'; +import { Button, Icon, Input, RadioButtonGroup, Switch, useStyles2 } from '@grafana/ui'; +import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; + +import { getDashboardSceneFor, getDefaultVizPanel } from '../../utils/utils'; +import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector'; +import { DashboardLayoutManager, EditableDashboardElement, LayoutParent } from '../types'; + +import { RowsLayoutManager } from './RowsLayoutManager'; + +export interface RowItemState extends SceneObjectState { + layout: DashboardLayoutManager; + title?: string; + isCollapsed?: boolean; + isHeaderHidden?: boolean; + height?: 'expand' | 'min'; +} + +export class RowItem extends SceneObjectBase implements LayoutParent, EditableDashboardElement { + public isEditableDashboardElement: true = true; + + public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] { + const row = this; + + const rowOptions = useMemo(() => { + return new OptionsPaneCategoryDescriptor({ + title: 'Row options', + id: 'row-options', + isOpenDefault: true, + }) + .addItem( + new OptionsPaneItemDescriptor({ + title: 'Title', + render: () => , + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: 'Height', + render: () => , + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: 'Hide row header', + render: () => , + }) + ); + }, [row]); + + const { layout } = this.useState(); + const layoutOptions = useLayoutCategory(layout); + + return [rowOptions, layoutOptions]; + } + + public getTypeName(): string { + return 'Row'; + } + + public onDelete = () => { + const layout = sceneGraph.getAncestor(this, RowsLayoutManager); + layout.removeRow(this); + }; + + public renderActions(): React.ReactNode { + return ( + <> + + + + + ); + } + + public getLayout(): DashboardLayoutManager { + return this.state.layout; + } + + public switchLayout(layout: DashboardLayoutManager): void { + this.setState({ layout }); + } + + public onCollapseToggle = () => { + this.setState({ isCollapsed: !this.state.isCollapsed }); + }; + + public onAddPanel = () => { + const vizPanel = getDefaultVizPanel(); + this.state.layout.addPanel(vizPanel); + }; + + public onEdit = () => { + const dashboard = getDashboardSceneFor(this); + dashboard.state.editPane.selectObject(this); + }; + + public static Component = ({ model }: SceneComponentProps) => { + const { layout, title, isCollapsed, height = 'expand' } = model.useState(); + const { isEditing } = getDashboardSceneFor(model).useState(); + const styles = useStyles2(getStyles); + const titleInterpolated = sceneGraph.interpolate(model, title, undefined, 'text'); + const ref = useRef(null); + const shouldGrow = !isCollapsed && height === 'expand'; + + return ( +
+
+ + {isEditing &&
+ {!isCollapsed && } +
+ ); + }; +} + +function getStyles(theme: GrafanaTheme2) { + return { + rowHeader: css({ + width: '100%', + display: 'flex', + gap: theme.spacing(1), + padding: theme.spacing(0, 0, 0.5, 0), + margin: theme.spacing(0, 0, 1, 0), + alignItems: 'center', + + '&:hover, &:focus-within': { + '& > div': { + opacity: 1, + }, + }, + + '& > div': { + marginBottom: 0, + marginRight: theme.spacing(1), + }, + }), + rowTitleButton: css({ + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + background: 'transparent', + border: 'none', + minWidth: 0, + gap: theme.spacing(1), + }), + rowTitle: css({ + fontSize: theme.typography.h5.fontSize, + fontWeight: theme.typography.fontWeightMedium, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '100%', + flexGrow: 1, + minWidth: 0, + }), + wrapper: css({ + display: 'flex', + flexDirection: 'column', + width: '100%', + }), + wrapperGrow: css({ + flexGrow: 1, + }), + wrapperCollapsed: css({ + flexGrow: 0, + borderBottom: `1px solid ${theme.colors.border.weak}`, + }), + rowActions: css({ + display: 'flex', + opacity: 0, + }), + }; +} + +export function RowTitleInput({ row }: { row: RowItem }) { + const { title } = row.useState(); + + return row.setState({ title: e.currentTarget.value })} />; +} + +export function RowHeaderSwitch({ row }: { row: RowItem }) { + const { isHeaderHidden } = row.useState(); + + return ( + { + row.setState({ + isHeaderHidden: !row.state.isHeaderHidden, + }); + }} + /> + ); +} + +export function RowHeightSelect({ row }: { row: RowItem }) { + const { height = 'expand' } = row.useState(); + + const options = [ + { label: 'Expand', value: 'expand' as const }, + { label: 'Min', value: 'min' as const }, + ]; + + return ( + + row.setState({ + height: option, + }) + } + /> + ); +} diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx new file mode 100644 index 00000000000..1fa3e29bdf2 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -0,0 +1,113 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { SceneComponentProps, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { useStyles2 } from '@grafana/ui'; + +import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager'; +import { DashboardLayoutManager, LayoutRegistryItem } from '../types'; + +import { RowItem } from './RowItem'; + +interface RowsLayoutManagerState extends SceneObjectState { + rows: RowItem[]; +} + +export class RowsLayoutManager extends SceneObjectBase implements DashboardLayoutManager { + public isDashboardLayoutManager: true = true; + + public editModeChanged(isEditing: boolean): void {} + + public addPanel(vizPanel: VizPanel): void {} + + public addNewRow(): void { + this.setState({ + rows: [ + ...this.state.rows, + new RowItem({ + title: 'New row', + layout: ResponsiveGridLayoutManager.createEmpty(), + }), + ], + }); + } + + public getNextPanelId(): number { + return 0; + } + + public removePanel(panel: VizPanel) {} + + public removeRow(row: RowItem) { + this.setState({ + rows: this.state.rows.filter((r) => r !== row), + }); + } + + public duplicatePanel(panel: VizPanel): void { + throw new Error('Method not implemented.'); + } + + public getVizPanels(): VizPanel[] { + const panels: VizPanel[] = []; + + for (const row of this.state.rows) { + const innerPanels = row.state.layout.getVizPanels(); + panels.push(...innerPanels); + } + + return panels; + } + + public getOptions() { + return []; + } + + public getDescriptor(): LayoutRegistryItem { + return RowsLayoutManager.getDescriptor(); + } + + public static getDescriptor(): LayoutRegistryItem { + return { + name: 'Rows', + description: 'Rows layout', + id: 'rows-layout', + createFromLayout: RowsLayoutManager.createFromLayout, + }; + } + + public static createEmpty() { + return new RowsLayoutManager({ rows: [] }); + } + + public static createFromLayout(layout: DashboardLayoutManager): RowsLayoutManager { + const row = new RowItem({ layout: layout.clone(), title: 'Row title' }); + + return new RowsLayoutManager({ rows: [row] }); + } + + public static Component = ({ model }: SceneComponentProps) => { + const { rows } = model.useState(); + const styles = useStyles2(getStyles); + + return ( +
+ {rows.map((row) => ( + + ))} +
+ ); + }; +} + +function getStyles(theme: GrafanaTheme2) { + return { + wrapper: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + height: '100%', + width: '100%', + }), + }; +} diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx new file mode 100644 index 00000000000..ff81e9c6ffc --- /dev/null +++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx @@ -0,0 +1,66 @@ +import { useMemo } from 'react'; + +import { Select } from '@grafana/ui'; +import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; + +import { DashboardLayoutManager, isLayoutParent, LayoutRegistryItem } from '../types'; + +import { layoutRegistry } from './layoutRegistry'; + +export interface Props { + layoutManager: DashboardLayoutManager; +} + +export function DashboardLayoutSelector({ layoutManager }: { layoutManager: DashboardLayoutManager }) { + const layouts = layoutRegistry.list(); + const options = layouts.map((layout) => ({ + label: layout.name, + value: layout, + })); + + const currentLayoutId = layoutManager.getDescriptor().id; + const currentLayoutOption = options.find((option) => option.value.id === currentLayoutId); + + return ( + changeLayoutTo(layoutManager, option.value!)} - /> -
- {layoutManager.renderEditor?.()} - - )} - {children} - - ); -} - -function getStyles(theme: GrafanaTheme2) { - return { - editHeader: css({ - width: '100%', - display: 'flex', - gap: theme.spacing(1), - padding: theme.spacing(0, 1, 0.5, 1), - margin: theme.spacing(0, 0, 1, 0), - alignItems: 'flex-end', - borderBottom: `1px solid ${theme.colors.border.weak}`, - paddingBottom: theme.spacing(1), - - '&:hover, &:focus-within': { - '& > div': { - opacity: 1, - }, - }, - - '& > div': { - marginBottom: 0, - marginRight: theme.spacing(1), - }, - }), - wrapper: css({ - display: 'flex', - flexDirection: 'column', - flex: '1 1 0', - width: '100%', - }), - icon: css({ - display: 'flex', - alignItems: 'center', - cursor: 'pointer', - background: 'transparent', - border: 'none', - gap: theme.spacing(1), - }), - rowTitle: css({}), - rowActions: css({ - display: 'flex', - opacity: 0, - [theme.transitions.handleMotion('no-preference', 'reduce')]: { - transition: 'opacity 200ms ease-in', - }, - - '&:hover, &:focus-within': { - opacity: 1, - }, - }), - }; -} - -function changeLayoutTo(currentLayout: DashboardLayoutManager, newLayoutDescriptor: LayoutRegistryItem) { - const layoutParent = currentLayout.parent; - if (layoutParent && isLayoutParent(layoutParent)) { - layoutParent.switchLayout(newLayoutDescriptor.createFromLayout(currentLayout)); - } -} diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/layoutRegistry.ts b/public/app/features/dashboard-scene/scene/layouts-shared/layoutRegistry.ts index 2fbd9e9ecc2..95f345e71b9 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/layoutRegistry.ts +++ b/public/app/features/dashboard-scene/scene/layouts-shared/layoutRegistry.ts @@ -2,8 +2,13 @@ import { Registry } from '@grafana/data'; import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager'; import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager'; +import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager'; import { LayoutRegistryItem } from '../types'; export const layoutRegistry: Registry = new Registry(() => { - return [DefaultGridLayoutManager.getDescriptor(), ResponsiveGridLayoutManager.getDescriptor()]; + return [ + DefaultGridLayoutManager.getDescriptor(), + ResponsiveGridLayoutManager.getDescriptor(), + RowsLayoutManager.getDescriptor(), + ]; }); diff --git a/public/app/features/dashboard-scene/scene/types.ts b/public/app/features/dashboard-scene/scene/types.ts index fd50c4e2e28..a399e2d5938 100644 --- a/public/app/features/dashboard-scene/scene/types.ts +++ b/public/app/features/dashboard-scene/scene/types.ts @@ -1,21 +1,20 @@ import { BusEventWithPayload, RegistryItem } from '@grafana/data'; import { SceneObject, VizPanel } from '@grafana/scenes'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; /** * A scene object that usually wraps an underlying layout * Dealing with all the state management and editing of the layout */ export interface DashboardLayoutManager extends SceneObject { + /** Marks it as a DashboardLayoutManager */ + isDashboardLayoutManager: true; /** * Notify the layout manager that the edit mode has changed * @param isEditing */ editModeChanged(isEditing: boolean): void; - /** - * Not sure we will need this in the long run, we should be able to handle this inside internally - */ - getNextPanelId(): number; /** * Remove an element / panel * @param element @@ -54,7 +53,11 @@ export interface DashboardLayoutManager extends SceneObject { /** * Renders options and layout actions */ - renderEditor?(): React.ReactNode; + getOptions?(): OptionsPaneItemDescriptor[]; +} + +export function isDashboardLayoutManager(obj: SceneObject): obj is DashboardLayoutManager { + return 'isDashboardLayoutManager' in obj; } /** @@ -73,10 +76,6 @@ export interface LayoutRegistryItem extends RegistryItem { createFromSaveModel?(saveModel: any): void; } -export interface LayoutEditorProps { - layoutManager: T; -} - /** * This interface is needed to support layouts existing on different levels of the scene (DashboardScene and inside the TabsLayoutManager) */ diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 8c421b83e34..34f10356027 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -19,12 +19,14 @@ import { SceneDataLayerProvider, SceneDataLayerControls, UserActionEvent, + SceneObjectState, } from '@grafana/scenes'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { DashboardDTO, DashboardDataDTO } from 'app/types'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; +import { DashboardEditPaneBehavior } from '../edit-pane/DashboardEditPaneBehavior'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; @@ -214,6 +216,32 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, }); } + const behaviorList: SceneObjectState['$behaviors'] = [ + new behaviors.CursorSync({ + sync: oldModel.graphTooltip, + }), + new behaviors.SceneQueryController(), + registerDashboardMacro, + registerPanelInteractionsReporter, + new DashboardEditPaneBehavior({}), + new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }), + preserveDashboardSceneStateInLocalStorage, + addPanelsOnLoadBehavior, + new DashboardScopesFacade({ + reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange, + uid: oldModel.uid, + }), + new DashboardReloadBehavior({ + reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange, + uid: oldModel.uid, + version: oldModel.version, + }), + ]; + + if (config.featureToggles.dashboardNewLayouts) { + behaviorList.push(new DashboardEditPaneBehavior({})); + } + const dashboardScene = new DashboardScene({ description: oldModel.description, editable: oldModel.editable, @@ -242,28 +270,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, UNSAFE_nowDelay: oldModel.timepicker?.nowDelay, }), $variables: variables, - $behaviors: [ - new behaviors.CursorSync({ - sync: oldModel.graphTooltip, - }), - new behaviors.SceneQueryController(), - registerDashboardMacro, - registerPanelInteractionsReporter, - new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }), - preserveDashboardSceneStateInLocalStorage, - addPanelsOnLoadBehavior, - new DashboardScopesFacade({ - reloadOnParamsChange: - config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange, - uid: oldModel.uid, - }), - new DashboardReloadBehavior({ - reloadOnParamsChange: - config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange, - uid: oldModel.uid, - version: oldModel.version, - }), - ], + $behaviors: behaviorList, $data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }), controls: new DashboardControls({ variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index d7a08c729d4..fd6169589f4 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -34,7 +34,7 @@ import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLay import { transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2'; -function setupDashboardScene(state: DashboardSceneState): DashboardScene { +function setupDashboardScene(state: Partial): DashboardScene { return new DashboardScene(state); } diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx index 3a387f084d7..00e9dd599ac 100644 --- a/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.tsx @@ -46,7 +46,12 @@ export default function ShareButton({ dashboard, panel }: { dashboard: Dashboard Share - @@ -248,10 +285,7 @@ export const AzureCredentialsForm = (props: Props) => { width={45} aria-label="Password" value={credentials.password || ''} - onChange={(event: ChangeEvent) => { - const value = event.target.value; - onInputChange({ property: 'password', value }); - }} + onChange={onPasswordChange} id="password" disabled={disabled} /> diff --git a/public/app/plugins/datasource/mssql/types.ts b/public/app/plugins/datasource/mssql/types.ts index 2397a0d31b6..4562abb7623 100644 --- a/public/app/plugins/datasource/mssql/types.ts +++ b/public/app/plugins/datasource/mssql/types.ts @@ -1,4 +1,4 @@ -import { DataSourceJsonData } from '@grafana/data'; +import { AzureCredentials } from '@grafana/azure-sdk'; import { SQLOptions } from '@grafana/sql'; import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types'; @@ -17,37 +17,13 @@ export enum MSSQLEncryptOptions { false = 'false', true = 'true', } - -export enum AzureCloud { - Public = 'AzureCloud', - None = '', -} - -export type ConcealedSecretType = symbol; - -export enum AzureAuthType { - MSI = 'msi', - CLIENT_SECRET = 'clientsecret', - AD_PASSWORD = 'ad-password', -} - -export interface AzureCredentialsType { - authType: AzureAuthType; - azureCloud?: string; - tenantId?: string; - clientId?: string; - clientSecret?: string | ConcealedSecretType; - userId?: string; - password?: string | ConcealedSecretType; -} - export interface MssqlOptions extends SQLOptions { authenticationType?: MSSQLAuthenticationType; encrypt?: MSSQLEncryptOptions; sslRootCertFile?: string; serverName?: string; connectionTimeout?: number; - azureCredentials?: AzureCredentialsType; + azureCredentials?: AzureCredentials; keytabFilePath?: string; credentialCache?: string; credentialCacheLookupFile?: string; @@ -60,15 +36,6 @@ export interface MssqlSecureOptions { password?: string; } -export type AzureAuthJSONDataType = DataSourceJsonData & { - azureCredentials: AzureCredentialsType; -}; - -export type AzureAuthSecureJSONDataType = { - azureClientSecret: undefined | string | ConcealedSecretType; - password: undefined | string | ConcealedSecretType; -}; - export type AzureAuthConfigType = { azureAuthIsSupported: boolean; azureAuthSettingsUI: (props: HttpSettingsBaseProps) => JSX.Element; diff --git a/public/app/plugins/panel/logs/panelcfg.cue b/public/app/plugins/panel/logs/panelcfg.cue index fd0f47172c8..341a6aed88c 100644 --- a/public/app/plugins/panel/logs/panelcfg.cue +++ b/public/app/plugins/panel/logs/panelcfg.cue @@ -26,15 +26,15 @@ composableKinds: PanelCfg: { version: [0, 0] schema: { Options: { - showLabels: bool - showCommonLabels: bool - showTime: bool - showLogContextToggle: bool - wrapLogMessage: bool - prettifyLogMessage: bool - enableLogDetails: bool - sortOrder: common.LogsSortOrder - dedupStrategy: common.LogsDedupStrategy + showLabels: bool + showCommonLabels: bool + showTime: bool + showLogContextToggle: bool + wrapLogMessage: bool + prettifyLogMessage: bool + enableLogDetails: bool + sortOrder: common.LogsSortOrder + dedupStrategy: common.LogsDedupStrategy enableInfiniteScrolling?: bool // TODO: figure out how to define callbacks onClickFilterLabel?: _ diff --git a/public/openapi3.json b/public/openapi3.json index 6f003b70069..117b43ca050 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -22639,7 +22639,8 @@ }, "summary": "Deletes user.", "tags": [ - "user" + "user", + "enterprise" ] }, "get": { @@ -22663,7 +22664,8 @@ }, "summary": "Fetches all users in UserSchema format.", "tags": [ - "users" + "users", + "enterprise" ] }, "post": { @@ -22690,7 +22692,8 @@ }, "summary": "Creates user.", "tags": [ - "users" + "users", + "enterprise" ] } }, @@ -22716,7 +22719,8 @@ }, "summary": "Gets user by id.", "tags": [ - "user" + "user", + "enterprise" ] } }, diff --git a/scripts/drone/steps/github.star b/scripts/drone/steps/github.star index d40ff829751..2325ba83a57 100644 --- a/scripts/drone/steps/github.star +++ b/scripts/drone/steps/github.star @@ -37,4 +37,6 @@ def github_app_generate_token_step(): "echo $(/usr/bin/github-app-external-token) > /github-app/token", ], "volumes": github_app_step_volumes(), + # forks or those without access would cause it to fail, but we can safely ignore it since there'll be no token. + "failure": "ignore", } diff --git a/yarn.lock b/yarn.lock index 3cc7d14ba08..c83a4fd1ab8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1629,10 +1629,10 @@ __metadata: languageName: node linkType: hard -"@bsull/augurs@npm:^0.6.0": - version: 0.6.0 - resolution: "@bsull/augurs@npm:0.6.0" - checksum: 10/0ba2ea0432f7d4c44ccec4d112e672f8d5d977407be42ff5995e1d6641b4b0238f97b9cdf13bc1fc066559bbd54e6fb00ad1f418014823a6c22af619e7a29c6a +"@bsull/augurs@npm:^0.7.0": + version: 0.7.0 + resolution: "@bsull/augurs@npm:0.7.0" + checksum: 10/7291b401f37fd2d120e97449eb2820a069ae744a04ba3f952fde20f31c80eeb221e0b8861d5853e62667ed6ed7b33f5028dbd442d491fe06183dd5a5ad6f725f languageName: node linkType: hard @@ -3790,11 +3790,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:5.28.1": - version: 5.28.1 - resolution: "@grafana/scenes-react@npm:5.28.1" +"@grafana/scenes-react@npm:^5.30.0": + version: 5.30.0 + resolution: "@grafana/scenes-react@npm:5.30.0" dependencies: - "@grafana/scenes": "npm:5.28.1" + "@grafana/scenes": "npm:5.30.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3805,13 +3805,13 @@ __metadata: "@grafana/ui": ^11.0.0 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/1979eebf0eea30550e9ffc1c9a766cd239914f5b79e0487ddf77d49006288c78f2e3f72bbe97782b6144902ef05e649f6f46197891811e3bbf809e6d016f0920 + checksum: 10/8d33b0024865e4a869cbba4dc7530c7494f4c2cb3615bbe7e2c9a912736fc7618848ab92c03941f0847f5edc9dc68a2d68bf99aac9ff95e0d40c85f1fa410088 languageName: node linkType: hard -"@grafana/scenes@npm:5.28.1": - version: 5.28.1 - resolution: "@grafana/scenes@npm:5.28.1" +"@grafana/scenes@npm:5.30.0, @grafana/scenes@npm:^5.30.0": + version: 5.30.0 + resolution: "@grafana/scenes@npm:5.30.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3828,7 +3828,7 @@ __metadata: "@grafana/ui": ">=10.4" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/a12ab38c048e886a880bff64c5b17ae959f34efea44bee06afa4acdc4dcde14ccafc7aa263df568f54d7d5dd165d40952f11ce0ee0c1daa3ad46ea435dbe00ad + checksum: 10/d226f523ef2b22eac0de26e7929dbe5d5775f297b0f0c07a4a1b5c792c224c72eab67312d43b7a88d85416c9e4c91f9064777d54e696fdf9da2366c70914b6b3 languageName: node linkType: hard @@ -17531,7 +17531,7 @@ __metadata: "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" "@betterer/eslint": "npm:5.4.0" - "@bsull/augurs": "npm:^0.6.0" + "@bsull/augurs": "npm:^0.7.0" "@cypress/webpack-preprocessor": "npm:6.0.2" "@emotion/css": "npm:11.13.5" "@emotion/eslint-plugin": "npm:11.12.0" @@ -17559,8 +17559,8 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:5.28.1" - "@grafana/scenes-react": "npm:5.28.1" + "@grafana/scenes": "npm:^5.30.0" + "@grafana/scenes-react": "npm:^5.30.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0"