pull/89891/head
Ryan McKinley 1 year ago
commit a115bb6214
  1. 7
      .github/workflows/trivy-scan.yml
  2. 4
      docs/sources/developers/plugins/plugin.schema.json
  3. 3
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  4. 2
      docs/sources/whatsnew/whats-new-in-v10-2.md
  5. 39
      go.mod
  6. 56
      go.sum
  7. 2
      go.work.sum
  8. 3
      packages/grafana-data/src/types/featureToggles.gen.ts
  9. 9
      packages/grafana-prometheus/src/datasource.ts
  10. 15
      packages/grafana-prometheus/src/querycache/QueryCache.test.ts
  11. 187
      packages/grafana-prometheus/src/querycache/QueryCache.ts
  12. 1
      packages/grafana-ui/package.json
  13. 56
      packages/grafana-ui/src/components/Combobox/Combobox.internal.story.tsx
  14. 84
      packages/grafana-ui/src/components/Combobox/Combobox.tsx
  15. 12
      packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx
  16. 3
      pkg/apiserver/builder/helper.go
  17. 10
      pkg/services/authz/zanzana.go
  18. 40
      pkg/services/authz/zanzana/client.go
  19. 1
      pkg/services/datasources/errors.go
  20. 8
      pkg/services/datasources/service/store.go
  21. 17
      pkg/services/datasources/service/store_test.go
  22. 19
      pkg/services/featuremgmt/registry.go
  23. 3
      pkg/services/featuremgmt/toggles_gen.csv
  24. 12
      pkg/services/featuremgmt/toggles_gen.go
  25. 20
      pkg/services/featuremgmt/toggles_gen.json
  26. 9
      pkg/services/ngalert/accesscontrol/fakes/rules.go
  27. 8
      pkg/services/ngalert/accesscontrol/rules.go
  28. 2
      pkg/services/ngalert/api/api.go
  29. 3
      pkg/services/ngalert/api/testing.go
  30. 5
      pkg/services/ngalert/models/alert_rule.go
  31. 3
      pkg/services/ngalert/notifier/silence_svc.go
  32. 9
      pkg/services/ngalert/notifier/silence_svc_test.go
  33. 3
      pkg/services/ngalert/provisioning/accesscontrol.go
  34. 5
      pkg/services/ngalert/provisioning/accesscontrol_test.go
  35. 4
      pkg/services/ngalert/provisioning/alert_rules_test.go
  36. 3
      pkg/services/ngalert/provisioning/testing.go
  37. 12
      pkg/storage/unified/resource/broadcaster.go
  38. 2
      pkg/storage/unified/resource/broadcaster_test.go
  39. 92
      pkg/storage/unified/resource/go.sum
  40. 11
      pkg/util/shortid_generator.go
  41. 29
      pkg/util/shortid_generator_test.go
  42. 12
      public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx
  43. 2
      public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx
  44. 24
      public/app/features/dashboard-scene/scene/Scopes/api.ts
  45. 28
      public/app/features/dashboard-scene/scene/Scopes/utils.ts
  46. 30
      public/app/features/explore/ContentOutline/ContentOutlineContext.tsx
  47. 10
      public/app/features/explore/Graph/ExploreGraph.tsx
  48. 291
      public/app/features/explore/Logs/Logs.test.tsx
  49. 1472
      public/app/features/explore/Logs/Logs.tsx
  50. 2
      public/app/features/explore/state/main.ts
  51. 5
      public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx
  52. 4
      public/app/plugins/datasource/tempo/datasource.ts
  53. 24
      public/app/plugins/datasource/tempo/metricsSummary.test.ts
  54. 10
      public/app/plugins/datasource/tempo/metricsSummary.ts
  55. 21
      public/locales/de-DE/grafana.json
  56. 21
      public/locales/es-ES/grafana.json
  57. 21
      public/locales/fr-FR/grafana.json
  58. 21
      public/locales/pt-BR/grafana.json
  59. 21
      public/locales/zh-Hans/grafana.json
  60. 51
      yarn.lock

@ -1,9 +1,14 @@
name: Trivy Scan name: Trivy Scan
on: on:
pull_request: pull_request:
# only run on PRs where go.mod/go.sum/etc have been updated
paths:
- go.*
push: push:
branches: branches:
- main - main
paths:
- go.*
jobs: jobs:
trivy-scan: trivy-scan:
@ -25,6 +30,8 @@ jobs:
vuln-type: 'os,library' vuln-type: 'os,library'
severity: 'CRITICAL,HIGH' severity: 'CRITICAL,HIGH'
trivyignores: .trivyignore trivyignores: .trivyignore
# for the PR check, ignore JS-related issues
skip-files: 'yarn.lock,package.json'
- name: Run Trivy vulnerability scanner (SARIF) - name: Run Trivy vulnerability scanner (SARIF)
uses: aquasecurity/trivy-action@0.22.0 uses: aquasecurity/trivy-action@0.22.0
with: with:

@ -72,6 +72,10 @@
"pr": { "pr": {
"type": "number", "type": "number",
"description": "GitHub pull request the plugin was built from" "description": "GitHub pull request the plugin was built from"
},
"build": {
"type": "number",
"description": "Build job number used to build this plugin."
} }
} }
}, },

@ -138,7 +138,6 @@ Experimental features might be changed or removed without prior notice.
| `pluginsFrontendSandbox` | Enables the plugins frontend sandbox | | `pluginsFrontendSandbox` | Enables the plugins frontend sandbox |
| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) | | `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) |
| `vizAndWidgetSplit` | Split panels between visualizations and widgets | | `vizAndWidgetSplit` | Split panels between visualizations and widgets |
| `prometheusIncrementalQueryInstrumentation` | Adds RudderStack events to incremental queries |
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers |
| `mlExpressions` | Enable support for Machine Learning in server-side expressions | | `mlExpressions` | Enable support for Machine Learning in server-side expressions |
| `metricsSummary` | Enables metrics summary queries in the Tempo data source | | `metricsSummary` | Enables metrics summary queries in the Tempo data source |
@ -182,7 +181,6 @@ Experimental features might be changed or removed without prior notice.
| `accessActionSets` | Introduces action sets for resource permissions | | `accessActionSets` | Introduces action sets for resource permissions |
| `disableNumericMetricsSortingInExpressions` | In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. | | `disableNumericMetricsSortingInExpressions` | In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. |
| `queryLibrary` | Enables Query Library feature in Explore | | `queryLibrary` | Enables Query Library feature in Explore |
| `autofixDSUID` | Automatically migrates invalid datasource UIDs |
| `logsExploreTableDefaultVisualization` | Sets the logs table as default visualisation in logs explore | | `logsExploreTableDefaultVisualization` | Sets the logs table as default visualisation in logs explore |
| `newDashboardSharingComponent` | Enables the new sharing drawer design | | `newDashboardSharingComponent` | Enables the new sharing drawer design |
| `alertingListViewV2` | Enables the new alert list view design | | `alertingListViewV2` | Enables the new alert list view design |
@ -191,6 +189,7 @@ Experimental features might be changed or removed without prior notice.
| `alertingCentralAlertHistory` | Enables the new central alert history. | | `alertingCentralAlertHistory` | Enables the new central alert history. |
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | | `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars |
| `pinNavItems` | Enables pinning of nav items | | `pinNavItems` | Enables pinning of nav items |
| `failWrongDSUID` | Throws an error if a datasource has an invalid UIDs |
| `databaseReadReplica` | Use a read replica for some database queries. | | `databaseReadReplica` | Use a read replica for some database queries. |
## Development feature toggles ## Development feature toggles

@ -379,7 +379,7 @@ _Generally available in all editions of Grafana_
Use the Grafana Alerting - Grafana OnCall integration to effortlessly connect alerts generated by Grafana Alerting with Grafana OnCall. From there, you can route them according to defined escalation chains and schedules. Use the Grafana Alerting - Grafana OnCall integration to effortlessly connect alerts generated by Grafana Alerting with Grafana OnCall. From there, you can route them according to defined escalation chains and schedules.
To learn more, refer to the [Grafana OnCall integration for Alerting documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/manage-contact-points/integrations/configure-oncall/), as well as the following video demo. To learn more, refer to the [Grafana OnCall integration for Alerting documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/manage-contact-points/integrations/configure-oncall/), as well as the following video demo.
{{< youtube id="abRn5I61hxs?rel=0" >}} {{< youtube id="abRn5I61hxs?rel=0" >}}

@ -33,7 +33,7 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // @grafana/plugins-platform-backend github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // @grafana/plugins-platform-backend
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group
github.com/alicebob/miniredis/v2 v2.30.1 // @grafana/alerting-backend github.com/alicebob/miniredis/v2 v2.30.1 // @grafana/alerting-backend
github.com/andybalholm/brotli v1.0.5 // @grafana/partner-datasources github.com/andybalholm/brotli v1.0.6 // @grafana/partner-datasources
github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics
github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad
github.com/aws/aws-sdk-go v1.51.31 // @grafana/aws-datasources github.com/aws/aws-sdk-go v1.51.31 // @grafana/aws-datasources
@ -122,9 +122,9 @@ require (
github.com/magefile/mage v1.15.0 // @grafana/grafana-release-guild github.com/magefile/mage v1.15.0 // @grafana/grafana-release-guild
github.com/matryer/is v1.4.0 // @grafana/grafana-as-code github.com/matryer/is v1.4.0 // @grafana/grafana-as-code
github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group
github.com/mattn/go-sqlite3 v1.14.19 // @grafana/grafana-backend-group github.com/mattn/go-sqlite3 v1.14.22 // @grafana/grafana-backend-group
github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend
github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246 // @grafana/grafana-bi-squad github.com/microsoft/go-mssqldb v1.7.0 // @grafana/grafana-bi-squad
github.com/mitchellh/mapstructure v1.5.0 //@grafana/identity-access-team github.com/mitchellh/mapstructure v1.5.0 //@grafana/identity-access-team
github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-backend github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-backend
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend
@ -329,7 +329,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/asmfmt v1.3.2 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect
github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
@ -383,7 +382,7 @@ require (
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/encoding v0.3.6 // indirect github.com/segmentio/encoding v0.3.6 // indirect
github.com/sergi/go-diff v1.3.1 // indirect github.com/sergi/go-diff v1.3.1 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
@ -411,7 +410,7 @@ require (
go.opentelemetry.io/otel/metric v1.26.0 // indirect go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect golang.org/x/term v0.21.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
@ -424,37 +423,47 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/kms v0.29.2 // indirect k8s.io/kms v0.29.2 // indirect
lukechampine.com/uint128 v1.3.0 // indirect modernc.org/libc v1.41.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/memory v1.7.2 // indirect
modernc.org/libc v1.22.4 // indirect modernc.org/sqlite v1.29.6 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.21.2 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/yaml v1.4.0 // indirect; @grafana-app-platform-squad sigs.k8s.io/yaml v1.4.0 // indirect; @grafana-app-platform-squad
) )
require github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240620135321-10b6011dd787
require ( require (
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/karlseguin/ccache/v3 v3.0.5 // indirect github.com/karlseguin/ccache/v3 v3.0.5 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/natefinch/wrap v0.2.0 // indirect github.com/natefinch/wrap v0.2.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/oklog/ulid/v2 v2.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pressly/goose/v3 v3.20.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sethvargo/go-retry v0.2.4 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/viper v1.18.2 // indirect github.com/spf13/viper v1.18.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
) )
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream

@ -1394,10 +1394,10 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0/go.mod h1:Y/HgrePTmGy9HjdSGTqZNa+apUpTVIEVKXJyARP2lrk= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0/go.mod h1:Y/HgrePTmGy9HjdSGTqZNa+apUpTVIEVKXJyARP2lrk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU= github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
@ -1526,8 +1526,9 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGn
github.com/alicebob/miniredis/v2 v2.30.1 h1:HM1rlQjq1bm9yQcsawJqSZBJ9AYgxvjkMsNtddh90+g= github.com/alicebob/miniredis/v2 v2.30.1 h1:HM1rlQjq1bm9yQcsawJqSZBJ9AYgxvjkMsNtddh90+g=
github.com/alicebob/miniredis/v2 v2.30.1/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg= github.com/alicebob/miniredis/v2 v2.30.1/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
@ -2333,6 +2334,8 @@ github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:t
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vpYI6DHvFO595rpQGooUjcyicjt9rOevldDdW79peV0= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vpYI6DHvFO595rpQGooUjcyicjt9rOevldDdW79peV0=
github.com/grafana/grafana/pkg/promlib v0.0.6 h1:FuRyHMIgVVXkLuJnCflNfk3gqJflmyiI+/ZuJ9MoAfY= github.com/grafana/grafana/pkg/promlib v0.0.6 h1:FuRyHMIgVVXkLuJnCflNfk3gqJflmyiI+/ZuJ9MoAfY=
github.com/grafana/grafana/pkg/promlib v0.0.6/go.mod h1:shFkrG1fQ/PPNRGhxAPNMLp0SAeG/jhqaLoG6n2191M= github.com/grafana/grafana/pkg/promlib v0.0.6/go.mod h1:shFkrG1fQ/PPNRGhxAPNMLp0SAeG/jhqaLoG6n2191M=
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240620135321-10b6011dd787 h1:hWkuJda3RC3EC45GfYArB6CweHSJ7efsrJOu1db1dsE=
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240620135321-10b6011dd787/go.mod h1:zOInHv2y6bsgm9bIMsCVDaz1XylqIVX9r4amH4iuWPE=
github.com/grafana/grafana/pkg/util/xorm v0.0.1 h1:72QZjxWIWpSeOF8ob4aMV058kfgZyeetkAB8dmeti2o= github.com/grafana/grafana/pkg/util/xorm v0.0.1 h1:72QZjxWIWpSeOF8ob4aMV058kfgZyeetkAB8dmeti2o=
github.com/grafana/grafana/pkg/util/xorm v0.0.1/go.mod h1:eNfbB9f2jM8o9RfwqwjY8SYm5tvowJ8Ly+iE4P9rXII= github.com/grafana/grafana/pkg/util/xorm v0.0.1/go.mod h1:eNfbB9f2jM8o9RfwqwjY8SYm5tvowJ8Ly+iE4P9rXII=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
@ -2540,7 +2543,6 @@ github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCM
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
@ -2552,7 +2554,6 @@ github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiw
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
@ -2623,7 +2624,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karlseguin/ccache/v3 v3.0.5 h1:hFX25+fxzNjsRlREYsoGNa2LoVEw5mPF8wkWq/UnevQ= github.com/karlseguin/ccache/v3 v3.0.5 h1:hFX25+fxzNjsRlREYsoGNa2LoVEw5mPF8wkWq/UnevQ=
github.com/karlseguin/ccache/v3 v3.0.5/go.mod h1:qxC372+Qn+IBj8Pe3KvGjHPj0sWwEF7AeZVhsNPZ6uY= github.com/karlseguin/ccache/v3 v3.0.5/go.mod h1:qxC372+Qn+IBj8Pe3KvGjHPj0sWwEF7AeZVhsNPZ6uY=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -2750,8 +2750,8 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -2759,8 +2759,8 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQth
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246 h1:KT4vTYcHqj5C5hMK5kSpyAk7MnFqfHVWLL4VqMq66S8= github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs=
github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= github.com/microsoft/go-mssqldb v1.7.0/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@ -2868,6 +2868,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@ -3154,8 +3156,9 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v1.7.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/shoenig/test v1.7.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
@ -4675,14 +4678,12 @@ k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSn
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
@ -4693,11 +4694,12 @@ modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWs
modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
@ -4713,39 +4715,41 @@ modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/libc v1.22.4 h1:wymSbZb0AlrjdAVX3cjreCHTPCpPARbQXNz6BHPzdwQ=
modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0=
modernc.org/sqlite v1.21.2 h1:ixuUG0QS413Vfzyx6FWx6PYTmHaOegTY+hjzhn7L+a0=
modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0= modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0=
modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4=
modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
modernc.org/tcl v1.13.2/go.mod h1:7CLiGIPo1M8Rv1Mitpv5akc2+8fxUd2y2UzC/MfMzy0= modernc.org/tcl v1.13.2/go.mod h1:7CLiGIPo1M8Rv1Mitpv5akc2+8fxUd2y2UzC/MfMzy0=
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

@ -1008,7 +1008,9 @@ github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY=
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk= github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk=
github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo= github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo=
github.com/jaegertracing/jaeger v1.41.0 h1:vVNky8dP46M2RjGaZ7qRENqylW+tBFay3h57N16Ip7M= github.com/jaegertracing/jaeger v1.41.0 h1:vVNky8dP46M2RjGaZ7qRENqylW+tBFay3h57N16Ip7M=

@ -82,7 +82,6 @@ export interface FeatureToggles {
sqlDatasourceDatabaseSelection?: boolean; sqlDatasourceDatabaseSelection?: boolean;
recordedQueriesMulti?: boolean; recordedQueriesMulti?: boolean;
vizAndWidgetSplit?: boolean; vizAndWidgetSplit?: boolean;
prometheusIncrementalQueryInstrumentation?: boolean;
logsExploreTableVisualisation?: boolean; logsExploreTableVisualisation?: boolean;
awsDatasourcesTempCredentials?: boolean; awsDatasourcesTempCredentials?: boolean;
transformationsRedesign?: boolean; transformationsRedesign?: boolean;
@ -180,7 +179,6 @@ export interface FeatureToggles {
disableNumericMetricsSortingInExpressions?: boolean; disableNumericMetricsSortingInExpressions?: boolean;
grafanaManagedRecordingRules?: boolean; grafanaManagedRecordingRules?: boolean;
queryLibrary?: boolean; queryLibrary?: boolean;
autofixDSUID?: boolean;
logsExploreTableDefaultVisualization?: boolean; logsExploreTableDefaultVisualization?: boolean;
newDashboardSharingComponent?: boolean; newDashboardSharingComponent?: boolean;
alertingListViewV2?: boolean; alertingListViewV2?: boolean;
@ -196,6 +194,7 @@ export interface FeatureToggles {
authZGRPCServer?: boolean; authZGRPCServer?: boolean;
openSearchBackendFlowEnabled?: boolean; openSearchBackendFlowEnabled?: boolean;
ssoSettingsLDAP?: boolean; ssoSettingsLDAP?: boolean;
failWrongDSUID?: boolean;
databaseReadReplica?: boolean; databaseReadReplica?: boolean;
zanzana?: boolean; zanzana?: boolean;
} }

@ -139,7 +139,6 @@ export class PrometheusDatasource
this.cache = new QueryCache({ this.cache = new QueryCache({
getTargetSignature: this.getPrometheusTargetSignature.bind(this), getTargetSignature: this.getPrometheusTargetSignature.bind(this),
overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow, overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow,
profileFunction: this.getPrometheusProfileData.bind(this),
}); });
// This needs to be here and cannot be static because of how annotations typing affects casting of data source // This needs to be here and cannot be static because of how annotations typing affects casting of data source
@ -162,14 +161,6 @@ export class PrometheusDatasource
return query.expr; return query.expr;
} }
getPrometheusProfileData(request: DataQueryRequest<PromQuery>, targ: PromQuery) {
return {
interval: targ.interval ?? request.interval,
expr: this.interpolateString(targ.expr),
datasource: 'Prometheus',
};
}
/** /**
* Get target signature for query caching * Get target signature for query caching
* @param request * @param request

@ -6,7 +6,7 @@ import { DataFrame, DataQueryRequest, DateTime, dateTime, TimeRange } from '@gra
import { QueryEditorMode } from '../querybuilder/shared/types'; import { QueryEditorMode } from '../querybuilder/shared/types';
import { PromQuery } from '../types'; import { PromQuery } from '../types';
import { DatasourceProfileData, QueryCache } from './QueryCache'; import { QueryCache } from './QueryCache';
import { IncrementalStorageDataFrameScenarios } from './QueryCacheTestData'; import { IncrementalStorageDataFrameScenarios } from './QueryCacheTestData';
// Will not interpolate vars! // Will not interpolate vars!
@ -60,14 +60,6 @@ const mockPromRequest = (request?: Partial<DataQueryRequest<PromQuery>>): DataQu
}; };
}; };
const getPromProfileData = (request: DataQueryRequest, targ: PromQuery): DatasourceProfileData => {
return {
expr: targ.expr,
interval: targ.interval ?? request.interval,
datasource: 'prom',
};
};
describe('QueryCache: Generic', function () { describe('QueryCache: Generic', function () {
it('instantiates', () => { it('instantiates', () => {
const storage = new QueryCache({ const storage = new QueryCache({
@ -192,7 +184,6 @@ describe('QueryCache: Prometheus', function () {
const storage = new QueryCache<PromQuery>({ const storage = new QueryCache<PromQuery>({
getTargetSignature: getPrometheusTargetSignature, getTargetSignature: getPrometheusTargetSignature,
overlapString: '10m', overlapString: '10m',
profileFunction: getPromProfileData,
}); });
const firstFrames = scenario.first.dataFrames as unknown as DataFrame[]; const firstFrames = scenario.first.dataFrames as unknown as DataFrame[];
const secondFrames = scenario.second.dataFrames as unknown as DataFrame[]; const secondFrames = scenario.second.dataFrames as unknown as DataFrame[];
@ -328,7 +319,6 @@ describe('QueryCache: Prometheus', function () {
const storage = new QueryCache<PromQuery>({ const storage = new QueryCache<PromQuery>({
getTargetSignature: getPrometheusTargetSignature, getTargetSignature: getPrometheusTargetSignature,
overlapString: '10m', overlapString: '10m',
profileFunction: getPromProfileData,
}); });
// Initial request with all data for time range // Initial request with all data for time range
@ -489,7 +479,6 @@ describe('QueryCache: Prometheus', function () {
const storage = new QueryCache<PromQuery>({ const storage = new QueryCache<PromQuery>({
getTargetSignature: getPrometheusTargetSignature, getTargetSignature: getPrometheusTargetSignature,
overlapString: '10m', overlapString: '10m',
profileFunction: getPromProfileData,
}); });
const cacheRequest = storage.requestInfo(request); const cacheRequest = storage.requestInfo(request);
expect(cacheRequest.requests[0]).toBe(request); expect(cacheRequest.requests[0]).toBe(request);
@ -501,7 +490,6 @@ describe('QueryCache: Prometheus', function () {
const storage = new QueryCache<PromQuery>({ const storage = new QueryCache<PromQuery>({
getTargetSignature: getPrometheusTargetSignature, getTargetSignature: getPrometheusTargetSignature,
overlapString: '10m', overlapString: '10m',
profileFunction: getPromProfileData,
}); });
const cacheRequest = storage.requestInfo(request); const cacheRequest = storage.requestInfo(request);
expect(cacheRequest.requests[0]).toBe(request); expect(cacheRequest.requests[0]).toBe(request);
@ -513,7 +501,6 @@ describe('QueryCache: Prometheus', function () {
const storage = new QueryCache<PromQuery>({ const storage = new QueryCache<PromQuery>({
getTargetSignature: getPrometheusTargetSignature, getTargetSignature: getPrometheusTargetSignature,
overlapString: '10m', overlapString: '10m',
profileFunction: getPromProfileData,
}); });
const cacheRequest = storage.requestInfo(request); const cacheRequest = storage.requestInfo(request);
expect(cacheRequest.requests[0]).toBe(request); expect(cacheRequest.requests[0]).toBe(request);

@ -9,8 +9,6 @@ import {
isValidDuration, isValidDuration,
parseDuration, parseDuration,
} from '@grafana/data'; } from '@grafana/data';
import { faro } from '@grafana/faro-web-sdk';
import { config, reportInteraction } from '@grafana/runtime';
import { amendTable, Table, trimTable } from '../gcopypaste/app/features/live/data/amendTimeSeries'; import { amendTable, Table, trimTable } from '../gcopypaste/app/features/live/data/amendTimeSeries';
import { PromQuery } from '../types'; import { PromQuery } from '../types';
@ -19,8 +17,6 @@ import { PromQuery } from '../types';
// (must be stable across query changes, time range changes / interval changes / panel resizes / template variable changes) // (must be stable across query changes, time range changes / interval changes / panel resizes / template variable changes)
type TargetIdent = string; type TargetIdent = string;
type RequestID = string;
// query + template variables + interval + raw time range // query + template variables + interval + raw time range
// used for full target cache busting -> full range re-query // used for full target cache busting -> full range re-query
type TargetSig = string; type TargetSig = string;
@ -44,22 +40,6 @@ export interface CacheRequestInfo<T extends SupportedQueryTypes> {
shouldCache: boolean; shouldCache: boolean;
} }
export interface DatasourceProfileData {
interval?: string;
expr: string;
datasource: string;
}
interface ProfileData extends DatasourceProfileData {
identity: string;
bytes: number | null;
dashboardUID: string;
panelId?: number;
from: string;
queryRangeSeconds: number;
refreshIntervalMs: number;
}
/** /**
* Get field identity * Get field identity
* This is the string used to uniquely identify a field within a "target" * This is the string used to uniquely identify a field within a "target"
@ -76,40 +56,12 @@ export const getFieldIdent = (field: Field) => `${field.type}|${field.name}|${JS
export class QueryCache<T extends SupportedQueryTypes> { export class QueryCache<T extends SupportedQueryTypes> {
private overlapWindowMs: number; private overlapWindowMs: number;
private getTargetSignature: (request: DataQueryRequest<T>, target: T) => string; private getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
private getProfileData?: (request: DataQueryRequest<T>, target: T) => DatasourceProfileData;
private perfObeserver?: PerformanceObserver;
private shouldProfile: boolean;
// send profile events every 10 minutes
sendEventsInterval = 60000 * 10;
pendingRequestIdsToTargSigs = new Map<RequestID, ProfileData>();
pendingAccumulatedEvents = new Map<
string,
{
requestCount: number;
savedBytesTotal: number;
initialRequestSize: number;
lastRequestSize: number;
panelId: string;
dashId: string;
expr: string;
refreshIntervalMs: number;
sent: boolean;
datasource: string;
from: string;
queryRangeSeconds: number;
}
>();
cache = new Map<TargetIdent, TargetCache>(); cache = new Map<TargetIdent, TargetCache>();
constructor(options: { constructor(options: {
getTargetSignature: (request: DataQueryRequest<T>, target: T) => string; getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
overlapString: string; overlapString: string;
profileFunction?: (request: DataQueryRequest<T>, target: T) => DatasourceProfileData;
}) { }) {
const unverifiedOverlap = options.overlapString; const unverifiedOverlap = options.overlapString;
if (isValidDuration(unverifiedOverlap)) { if (isValidDuration(unverifiedOverlap)) {
@ -120,132 +72,9 @@ export class QueryCache<T extends SupportedQueryTypes> {
this.overlapWindowMs = durationToMilliseconds(duration); this.overlapWindowMs = durationToMilliseconds(duration);
} }
if (
(config.grafanaJavascriptAgent.enabled || config.featureToggles?.prometheusIncrementalQueryInstrumentation) &&
options.profileFunction !== undefined
) {
this.profile();
this.shouldProfile = true;
} else {
this.shouldProfile = false;
}
this.getProfileData = options.profileFunction;
this.getTargetSignature = options.getTargetSignature; this.getTargetSignature = options.getTargetSignature;
} }
private profile() {
// Check if PerformanceObserver is supported, and if we have Faro enabled for internal profiling
if (typeof PerformanceObserver === 'function') {
this.perfObeserver = new PerformanceObserver((list: PerformanceObserverEntryList) => {
list.getEntries().forEach((entry) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const entryTypeCast: PerformanceResourceTiming = entry as PerformanceResourceTiming;
// Safari support for this is coming in 16.4:
// https://caniuse.com/mdn-api_performanceresourcetiming_transfersize
// Gating that this exists to prevent runtime errors
const isSupported = typeof entryTypeCast?.transferSize === 'number';
if (entryTypeCast?.initiatorType === 'fetch' && isSupported) {
let fetchUrl = entryTypeCast.name;
if (fetchUrl.includes('/api/ds/query')) {
let match = fetchUrl.match(/requestId=([a-z\d]+)/i);
if (match) {
let requestId = match[1];
const requestTransferSize = Math.round(entryTypeCast.transferSize);
const currentRequest = this.pendingRequestIdsToTargSigs.get(requestId);
if (currentRequest) {
const entries = this.pendingRequestIdsToTargSigs.entries();
for (let [, value] of entries) {
if (value.identity === currentRequest.identity && value.bytes !== null) {
const previous = this.pendingAccumulatedEvents.get(value.identity);
const savedBytes = value.bytes - requestTransferSize;
this.pendingAccumulatedEvents.set(value.identity, {
datasource: value.datasource ?? 'N/A',
requestCount: (previous?.requestCount ?? 0) + 1,
savedBytesTotal: (previous?.savedBytesTotal ?? 0) + savedBytes,
initialRequestSize: value.bytes,
lastRequestSize: requestTransferSize,
panelId: currentRequest.panelId?.toString() ?? '',
dashId: currentRequest.dashboardUID ?? '',
expr: currentRequest.expr ?? '',
refreshIntervalMs: currentRequest.refreshIntervalMs ?? 0,
sent: false,
from: currentRequest.from ?? '',
queryRangeSeconds: currentRequest.queryRangeSeconds ?? 0,
});
// We don't need to save each subsequent request, only the first one
this.pendingRequestIdsToTargSigs.delete(requestId);
return;
}
}
// If we didn't return above, this should be the first request, let's save the observed size
this.pendingRequestIdsToTargSigs.set(requestId, { ...currentRequest, bytes: requestTransferSize });
}
}
}
}
});
});
this.perfObeserver.observe({ type: 'resource', buffered: false });
setInterval(this.sendPendingTrackingEvents, this.sendEventsInterval);
// Send any pending profile information when the user navigates away
window.addEventListener('beforeunload', this.sendPendingTrackingEvents);
}
}
sendPendingTrackingEvents = () => {
const entries = this.pendingAccumulatedEvents.entries();
for (let [key, value] of entries) {
if (!value.sent) {
const event = {
datasource: value.datasource.toString(),
requestCount: value.requestCount.toString(),
savedBytesTotal: value.savedBytesTotal.toString(),
initialRequestSize: value.initialRequestSize.toString(),
lastRequestSize: value.lastRequestSize.toString(),
panelId: value.panelId.toString(),
dashId: value.dashId.toString(),
expr: value.expr.toString(),
refreshIntervalMs: value.refreshIntervalMs.toString(),
from: value.from.toString(),
queryRangeSeconds: value.queryRangeSeconds.toString(),
};
if (config.featureToggles.prometheusIncrementalQueryInstrumentation) {
reportInteraction('grafana_incremental_queries_profile', event);
} else if (faro.api.pushEvent) {
faro.api.pushEvent('incremental query response size', event, 'no-interaction', {
skipDedupe: true,
});
}
this.pendingAccumulatedEvents.set(key, {
...value,
sent: true,
requestCount: 0,
savedBytesTotal: 0,
initialRequestSize: 0,
lastRequestSize: 0,
});
}
}
};
// can be used to change full range request to partial, split into multiple requests // can be used to change full range request to partial, split into multiple requests
requestInfo(request: DataQueryRequest<T>): CacheRequestInfo<T> { requestInfo(request: DataQueryRequest<T>): CacheRequestInfo<T> {
// TODO: align from/to to interval to increase probability of hitting backend cache // TODO: align from/to to interval to increase probability of hitting backend cache
@ -260,27 +89,11 @@ export class QueryCache<T extends SupportedQueryTypes> {
let doPartialQuery = shouldCache; let doPartialQuery = shouldCache;
let prevTo: TimestampMs | undefined = undefined; let prevTo: TimestampMs | undefined = undefined;
const refreshIntervalMs = request.intervalMs;
// pre-compute reqTargSigs // pre-compute reqTargSigs
const reqTargSigs = new Map<TargetIdent, TargetSig>(); const reqTargSigs = new Map<TargetIdent, TargetSig>();
request.targets.forEach((targ) => { request.targets.forEach((targ) => {
let targIdent = `${request.dashboardUID}|${request.panelId}|${targ.refId}`; let targIdent = `${request.dashboardUID}|${request.panelId}|${targ.refId}`;
let targSig = this.getTargetSignature(request, targ); // ${request.maxDataPoints} ? let targSig = this.getTargetSignature(request, targ); // ${request.maxDataPoints} ?
if (this.shouldProfile && this.getProfileData) {
this.pendingRequestIdsToTargSigs.set(request.requestId, {
...this.getProfileData(request, targ),
identity: targIdent + '|' + targSig,
bytes: null,
panelId: request.panelId,
dashboardUID: request.dashboardUID ?? '',
from: request.rangeRaw?.from.toString() ?? '',
queryRangeSeconds: request.range.to.diff(request.range.from, 'seconds') ?? '',
refreshIntervalMs: refreshIntervalMs ?? 0,
});
}
reqTargSigs.set(targIdent, targSig); reqTargSigs.set(targIdent, targSig);
}); });

@ -61,6 +61,7 @@
"@react-aria/focus": "3.17.1", "@react-aria/focus": "3.17.1",
"@react-aria/overlays": "3.22.1", "@react-aria/overlays": "3.22.1",
"@react-aria/utils": "3.24.1", "@react-aria/utils": "3.24.1",
"@tanstack/react-virtual": "^3.5.1",
"ansicolor": "1.1.100", "ansicolor": "1.1.100",
"calculate-size": "1.1.1", "calculate-size": "1.1.1",
"classnames": "2.5.1", "classnames": "2.5.1",

@ -1,10 +1,15 @@
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { Meta, StoryFn } from '@storybook/react'; import { Meta, StoryFn, StoryObj } from '@storybook/react';
import React, { useState } from 'react'; import { Chance } from 'chance';
import React, { ComponentProps, useMemo, useState } from 'react';
import { Combobox } from './Combobox'; import { Combobox, Option, Value } from './Combobox';
const meta: Meta<typeof Combobox> = { const chance = new Chance();
type PropsAndCustomArgs = ComponentProps<typeof Combobox> & { numberOfOptions: number };
const meta: Meta<PropsAndCustomArgs> = {
title: 'Forms/Combobox', title: 'Forms/Combobox',
component: Combobox, component: Combobox,
args: { args: {
@ -28,9 +33,11 @@ const meta: Meta<typeof Combobox> = {
], ],
value: 'banana', value: 'banana',
}, },
render: (args) => <BasicWithState {...args} />,
}; };
export const Basic: StoryFn<typeof Combobox> = (args) => { const BasicWithState: StoryFn<typeof Combobox> = (args) => {
const [value, setValue] = useState(args.value); const [value, setValue] = useState(args.value);
return ( return (
<Combobox <Combobox
@ -44,4 +51,43 @@ export const Basic: StoryFn<typeof Combobox> = (args) => {
); );
}; };
type Story = StoryObj<typeof Combobox>;
export const Basic: Story = {};
function generateOptions(amount: number): Option[] {
return Array.from({ length: amount }, () => ({
label: chance.name(),
value: chance.guid(),
description: chance.sentence(),
}));
}
const manyOptions = generateOptions(1e5);
manyOptions.push({ label: 'Banana', value: 'banana', description: 'A yellow fruit' });
const ManyOptionsStory: StoryFn<PropsAndCustomArgs> = ({ numberOfOptions }) => {
const [value, setValue] = useState<Value>(manyOptions[5].value);
const options = useMemo(() => generateOptions(numberOfOptions), [numberOfOptions]);
return (
<Combobox
options={options}
value={value}
onChange={(val) => {
setValue(val.value);
action('onChange')(val);
}}
/>
);
};
export const ManyOptions: StoryObj<PropsAndCustomArgs> = {
args: {
numberOfOptions: 1e5,
options: undefined,
value: undefined,
},
render: ManyOptionsStory,
};
export default meta; export default meta;

@ -1,13 +1,17 @@
import { css } from '@emotion/css';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useCombobox } from 'downshift'; import { useCombobox } from 'downshift';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { useStyles2 } from '../../themes';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
import { Input, Props as InputProps } from '../Input/Input'; import { Input, Props as InputProps } from '../Input/Input';
type Value = string | number; export type Value = string | number;
type Option = { export type Option = {
label: string; label: string;
value: Value; value: Value;
description?: string;
}; };
interface ComboboxProps interface ComboboxProps
@ -33,32 +37,86 @@ function itemFilter(inputValue: string) {
}; };
} }
function estimateSize() {
return 60;
}
export const Combobox = ({ options, onChange, value, ...restProps }: ComboboxProps) => { export const Combobox = ({ options, onChange, value, ...restProps }: ComboboxProps) => {
const [items, setItems] = useState(options); const [items, setItems] = useState(options);
const selectedItem = useMemo(() => options.find((option) => option.value === value) || null, [options, value]); const selectedItem = useMemo(() => options.find((option) => option.value === value) || null, [options, value]);
const listRef = useRef(null);
const styles = useStyles2(getStyles);
const rowVirtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => listRef.current,
estimateSize,
overscan: 2,
});
const { getInputProps, getMenuProps, getItemProps, isOpen } = useCombobox({ const { getInputProps, getMenuProps, getItemProps, isOpen } = useCombobox({
items, items,
itemToString, itemToString,
selectedItem, selectedItem,
scrollIntoView: () => {},
onInputValueChange: ({ inputValue }) => { onInputValueChange: ({ inputValue }) => {
setItems(options.filter(itemFilter(inputValue))); setItems(options.filter(itemFilter(inputValue)));
}, },
onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem), onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem),
onHighlightedIndexChange: ({ highlightedIndex, type }) => {
if (type !== useCombobox.stateChangeTypes.MenuMouseLeave) {
rowVirtualizer.scrollToIndex(highlightedIndex);
}
},
}); });
return ( return (
<div> <div>
<Input suffix={<Icon name={isOpen ? 'search' : 'angle-down'} />} {...restProps} {...getInputProps()} /> <Input suffix={<Icon name={isOpen ? 'search' : 'angle-down'} />} {...restProps} {...getInputProps()} />
<ul {...getMenuProps()}> <div className={styles.dropdown} {...getMenuProps({ ref: listRef })}>
{isOpen && {isOpen && (
items.map((item, index) => { <ul style={{ height: rowVirtualizer.getTotalSize() }}>
return ( {rowVirtualizer.getVirtualItems().map((virtualRow) => {
<li key={item.value} {...getItemProps({ item, index })}> return (
{item.label} <li
</li> key={items[virtualRow.index].value}
); {...getItemProps({ item: items[virtualRow.index], index: virtualRow.index })}
})} data-index={virtualRow.index}
</ul> ref={rowVirtualizer.measureElement}
className={styles.menuItem}
style={{
transform: `translateY(${virtualRow.start}px)`,
}}
>
<span>{items[virtualRow.index].label}</span>
{items[virtualRow.index].description && <span>{items[virtualRow.index].description}</span>}
</li>
);
})}
</ul>
)}
</div>
</div> </div>
); );
}; };
const getStyles = () => ({
dropdown: css({
position: 'absolute',
height: 400,
width: 600,
overflowY: 'scroll',
contain: 'strict',
}),
menuItem: css({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
'&:first-child': {
fontWeight: 'bold',
},
}),
});

@ -135,6 +135,7 @@ export function PanelChrome({
const theme = useTheme2(); const theme = useTheme2();
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const panelContentId = useId(); const panelContentId = useId();
const panelTitleId = useId().replace(/:/g, '_');
const hasHeader = !hoverHeader; const hasHeader = !hoverHeader;
@ -179,7 +180,13 @@ export function PanelChrome({
{/* Non collapsible title */} {/* Non collapsible title */}
{!collapsible && title && ( {!collapsible && title && (
<div className={styles.title}> <div className={styles.title}>
<Text element="h2" variant="h6" truncate title={typeof title === 'string' ? title : undefined}> <Text
element="h2"
variant="h6"
truncate
title={typeof title === 'string' ? title : undefined}
id={panelTitleId}
>
{title} {title}
</Text> </Text>
</div> </div>
@ -206,7 +213,7 @@ export function PanelChrome({
aria-hidden={!!title} aria-hidden={!!title}
aria-label={!title ? 'toggle collapse panel' : undefined} aria-label={!title ? 'toggle collapse panel' : undefined}
/> />
<Text variant="h6" truncate> <Text variant="h6" truncate id={panelTitleId}>
{title} {title}
</Text> </Text>
</button> </button>
@ -249,6 +256,7 @@ export function PanelChrome({
<section <section
className={cx(styles.container, { [styles.transparentContainer]: isPanelTransparent })} className={cx(styles.container, { [styles.transparentContainer]: isPanelTransparent })}
style={containerStyles} style={containerStyles}
aria-labelledby={!!title ? panelTitleId : undefined}
data-testid={testid} data-testid={testid}
tabIndex={0} // eslint-disable-line jsx-a11y/no-noninteractive-tabindex tabIndex={0} // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
onFocus={onFocus} onFocus={onFocus}

@ -14,6 +14,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/version" "k8s.io/apimachinery/pkg/version"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
@ -99,6 +100,8 @@ func SetupConfig(
handler = filters.WithAcceptHeader(handler) handler = filters.WithAcceptHeader(handler)
handler = filters.WithPathRewriters(handler, pathRewriters) handler = filters.WithPathRewriters(handler, pathRewriters)
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI") handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
// Configure filters.WithPanicRecovery to not crash on panic
utilruntime.ReallyCrash = false
return handler return handler
} }

@ -21,25 +21,23 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
type ZanzanaClient interface{}
// ProvideZanzana used to register ZanzanaClient. // ProvideZanzana used to register ZanzanaClient.
// It will also start an embedded ZanzanaSever if mode is set to "embedded". // It will also start an embedded ZanzanaSever if mode is set to "embedded".
func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureToggles) (ZanzanaClient, error) { func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureToggles) (zanzana.Client, error) {
if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) { if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
return zanzana.NoopClient{}, nil return zanzana.NoopClient{}, nil
} }
logger := log.New("zanzana") logger := log.New("zanzana")
var client *zanzana.Client var client zanzana.Client
switch cfg.Zanzana.Mode { switch cfg.Zanzana.Mode {
case setting.ZanzanaModeClient: case setting.ZanzanaModeClient:
conn, err := grpc.NewClient(cfg.Zanzana.Addr, grpc.WithTransportCredentials(insecure.NewCredentials())) conn, err := grpc.NewClient(cfg.Zanzana.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err) return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
} }
client = zanzana.NewClient(openfgav1.NewOpenFGAServiceClient(conn)) client = zanzana.NewClient(conn)
case setting.ZanzanaModeEmbedded: case setting.ZanzanaModeEmbedded:
store, err := zanzana.NewEmbeddedStore(cfg, db, logger) store, err := zanzana.NewEmbeddedStore(cfg, db, logger)
if err != nil { if err != nil {
@ -53,7 +51,7 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
channel := &inprocgrpc.Channel{} channel := &inprocgrpc.Channel{}
openfgav1.RegisterOpenFGAServiceServer(channel, srv) openfgav1.RegisterOpenFGAServiceServer(channel, srv)
client = zanzana.NewClient(openfgav1.NewOpenFGAServiceClient(channel)) client = zanzana.NewClient(channel)
default: default:
return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.Zanzana.Mode) return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.Zanzana.Mode)
} }

@ -1,16 +1,46 @@
package zanzana package zanzana
import ( import (
"context"
openfgav1 "github.com/openfga/api/proto/openfga/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1"
"google.golang.org/grpc"
"github.com/grafana/grafana/pkg/infra/log"
) )
// FIXME(kalleep): Build out our wrapper client for openFGA // Client is a wrapper around OpenFGAServiceClient with only methods using in Grafana included.
type Client struct { type Client interface {
c openfgav1.OpenFGAServiceClient Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error)
ListObjects(ctx context.Context, in *openfgav1.ListObjectsRequest, opts ...grpc.CallOption) (*openfgav1.ListObjectsResponse, error)
}
type zanzanaClient struct {
client openfgav1.OpenFGAServiceClient
logger log.Logger
}
func NewClient(cc grpc.ClientConnInterface) Client {
return &zanzanaClient{
client: openfgav1.NewOpenFGAServiceClient(cc),
logger: log.New("zanzana-client"),
}
} }
func NewClient(c openfgav1.OpenFGAServiceClient) *Client { func (c *zanzanaClient) Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error) {
return &Client{c} return c.client.Check(ctx, in, opts...)
}
func (c *zanzanaClient) ListObjects(ctx context.Context, in *openfgav1.ListObjectsRequest, opts ...grpc.CallOption) (*openfgav1.ListObjectsResponse, error) {
return c.client.ListObjects(ctx, in, opts...)
} }
type NoopClient struct{} type NoopClient struct{}
func (nc NoopClient) Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error) {
return nil, nil
}
func (nc NoopClient) ListObjects(ctx context.Context, in *openfgav1.ListObjectsRequest, opts ...grpc.CallOption) (*openfgav1.ListObjectsResponse, error) {
return nil, nil
}

@ -18,4 +18,5 @@ var (
ErrDataSourceNameInvalid = errutil.ValidationFailed("datasource.nameInvalid", errutil.WithPublicMessage("Invalid datasource name.")) ErrDataSourceNameInvalid = errutil.ValidationFailed("datasource.nameInvalid", errutil.WithPublicMessage("Invalid datasource name."))
ErrDataSourceURLInvalid = errutil.ValidationFailed("datasource.urlInvalid", errutil.WithPublicMessage("Invalid datasource url.")) ErrDataSourceURLInvalid = errutil.ValidationFailed("datasource.urlInvalid", errutil.WithPublicMessage("Invalid datasource url."))
ErrDataSourceAPIVersionInvalid = errutil.ValidationFailed("datasource.apiVersionInvalid", errutil.WithPublicMessage("Invalid datasource apiVersion.")) ErrDataSourceAPIVersionInvalid = errutil.ValidationFailed("datasource.apiVersionInvalid", errutil.WithPublicMessage("Invalid datasource apiVersion."))
ErrDataSourceUIDInvalid = errutil.ValidationFailed("datasource.uidInvalid", errutil.WithPublicMessage("Invalid datasource UID."))
) )

@ -252,8 +252,8 @@ func (ss *SqlStore) AddDataSource(ctx context.Context, cmd *datasources.AddDataS
cmd.UID = uid cmd.UID = uid
} else if err := util.ValidateUID(cmd.UID); err != nil { } else if err := util.ValidateUID(cmd.UID); err != nil {
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "create", err) logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "create", err)
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagAutofixDSUID) { if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagFailWrongDSUID) {
return fmt.Errorf("invalid UID for datasource %s: %w", cmd.Name, err) return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
} }
} }
@ -329,8 +329,8 @@ func (ss *SqlStore) UpdateDataSource(ctx context.Context, cmd *datasources.Updat
if cmd.UID != "" { if cmd.UID != "" {
if err := util.ValidateUID(cmd.UID); err != nil { if err := util.ValidateUID(cmd.UID); err != nil {
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "update", err) logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "update", err)
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagAutofixDSUID) { if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagFailWrongDSUID) {
cmd.UID = util.AutofixUID(cmd.UID) return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
} }
} }
} }

@ -104,7 +104,7 @@ func TestIntegrationDataAccess(t *testing.T) {
ss := SqlStore{ ss := SqlStore{
db: db, db: db,
logger: log.NewNopLogger(), logger: log.NewNopLogger(),
features: featuremgmt.WithFeatures(featuremgmt.FlagAutofixDSUID), features: featuremgmt.WithFeatures(featuremgmt.FlagFailWrongDSUID),
} }
cmd := defaultAddDatasourceCommand cmd := defaultAddDatasourceCommand
cmd.UID = "test/uid" cmd.UID = "test/uid"
@ -232,28 +232,21 @@ func TestIntegrationDataAccess(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("updates UID with a valid one", func(t *testing.T) { t.Run("fails to update a datasource with an invalid uid", func(t *testing.T) {
db := db.InitTestDB(t) db := db.InitTestDB(t)
ds := initDatasource(db) ds := initDatasource(db)
ss := SqlStore{ ss := SqlStore{
db: db, db: db,
logger: log.NewNopLogger(), logger: log.NewNopLogger(),
features: featuremgmt.WithFeatures(featuremgmt.FlagAutofixDSUID), features: featuremgmt.WithFeatures(featuremgmt.FlagFailWrongDSUID),
} }
require.NotEmpty(t, ds.UID) require.NotEmpty(t, ds.UID)
cmd := defaultUpdateDatasourceCommand cmd := defaultUpdateDatasourceCommand
cmd.ID = ds.ID cmd.ID = ds.ID
cmd.UID = "new/uid" cmd.UID = "new/uid"
res, err := ss.UpdateDataSource(context.Background(), &cmd) _, err := ss.UpdateDataSource(context.Background(), &cmd)
require.NoError(t, err) require.ErrorContains(t, err, "invalid format of UID")
require.Equal(t, "new-uid", res.UID)
// Return the datasource with the valid UID
query := datasources.GetDataSourceQuery{UID: "new-uid", OrgID: 10}
dataSource, err := ss.GetDataSource(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, "new-uid", dataSource.UID)
}) })
}) })

@ -480,13 +480,6 @@ var (
FrontendOnly: true, FrontendOnly: true,
Owner: grafanaDashboardsSquad, Owner: grafanaDashboardsSquad,
}, },
{
Name: "prometheusIncrementalQueryInstrumentation",
Description: "Adds RudderStack events to incremental queries",
FrontendOnly: true,
Stage: FeatureStageExperimental,
Owner: grafanaObservabilityMetricsSquad,
},
{ {
Name: "logsExploreTableVisualisation", Name: "logsExploreTableVisualisation",
Description: "A table visualisation for logs in Explore", Description: "A table visualisation for logs in Explore",
@ -1222,12 +1215,6 @@ var (
FrontendOnly: false, FrontendOnly: false,
AllowSelfServe: false, AllowSelfServe: false,
}, },
{
Name: "autofixDSUID",
Description: "Automatically migrates invalid datasource UIDs",
Stage: FeatureStageExperimental,
Owner: grafanaPluginsPlatformSquad,
},
{ {
Name: "logsExploreTableDefaultVisualization", Name: "logsExploreTableDefaultVisualization",
Description: "Sets the logs table as default visualisation in logs explore", Description: "Sets the logs table as default visualisation in logs explore",
@ -1336,6 +1323,12 @@ var (
HideFromDocs: true, HideFromDocs: true,
HideFromAdminPage: true, HideFromAdminPage: true,
}, },
{
Name: "failWrongDSUID",
Description: "Throws an error if a datasource has an invalid UIDs",
Stage: FeatureStageExperimental,
Owner: grafanaPluginsPlatformSquad,
},
{ {
Name: "databaseReadReplica", Name: "databaseReadReplica",
Description: "Use a read replica for some database queries.", Description: "Use a read replica for some database queries.",

@ -63,7 +63,6 @@ frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,
sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true
recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false
vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,true vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,true
prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,true
logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true
awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false
transformationsRedesign,GA,@grafana/observability-metrics,false,false,true transformationsRedesign,GA,@grafana/observability-metrics,false,false,true
@ -161,7 +160,6 @@ accessActionSets,experimental,@grafana/identity-access-team,false,false,false
disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false
grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false
queryLibrary,experimental,@grafana/explore-squad,false,false,false queryLibrary,experimental,@grafana/explore-squad,false,false,false
autofixDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true
newDashboardSharingComponent,experimental,@grafana/sharing-squad,false,false,true newDashboardSharingComponent,experimental,@grafana/sharing-squad,false,false,true
alertingListViewV2,experimental,@grafana/alerting-squad,false,false,true alertingListViewV2,experimental,@grafana/alerting-squad,false,false,true
@ -177,5 +175,6 @@ pinNavItems,experimental,@grafana/grafana-frontend-platform,false,false,false
authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false
openSearchBackendFlowEnabled,preview,@grafana/aws-datasources,false,false,false openSearchBackendFlowEnabled,preview,@grafana/aws-datasources,false,false,false
ssoSettingsLDAP,experimental,@grafana/identity-access-team,false,false,false ssoSettingsLDAP,experimental,@grafana/identity-access-team,false,false,false
failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,false,false,false databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,false,false,false
zanzana,experimental,@grafana/identity-access-team,false,false,false zanzana,experimental,@grafana/identity-access-team,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
63 sqlDatasourceDatabaseSelection preview @grafana/dataviz-squad false false true
64 recordedQueriesMulti GA @grafana/observability-metrics false false false
65 vizAndWidgetSplit experimental @grafana/dashboards-squad false false true
prometheusIncrementalQueryInstrumentation experimental @grafana/observability-metrics false false true
66 logsExploreTableVisualisation GA @grafana/observability-logs false false true
67 awsDatasourcesTempCredentials experimental @grafana/aws-datasources false false false
68 transformationsRedesign GA @grafana/observability-metrics false false true
160 disableNumericMetricsSortingInExpressions experimental @grafana/observability-metrics false true false
161 grafanaManagedRecordingRules experimental @grafana/alerting-squad false false false
162 queryLibrary experimental @grafana/explore-squad false false false
autofixDSUID experimental @grafana/plugins-platform-backend false false false
163 logsExploreTableDefaultVisualization experimental @grafana/observability-logs false false true
164 newDashboardSharingComponent experimental @grafana/sharing-squad false false true
165 alertingListViewV2 experimental @grafana/alerting-squad false false true
175 authZGRPCServer experimental @grafana/identity-access-team false false false
176 openSearchBackendFlowEnabled preview @grafana/aws-datasources false false false
177 ssoSettingsLDAP experimental @grafana/identity-access-team false false false
178 failWrongDSUID experimental @grafana/plugins-platform-backend false false false
179 databaseReadReplica experimental @grafana/grafana-backend-services-squad false false false
180 zanzana experimental @grafana/identity-access-team false false false

@ -263,10 +263,6 @@ const (
// Split panels between visualizations and widgets // Split panels between visualizations and widgets
FlagVizAndWidgetSplit = "vizAndWidgetSplit" FlagVizAndWidgetSplit = "vizAndWidgetSplit"
// FlagPrometheusIncrementalQueryInstrumentation
// Adds RudderStack events to incremental queries
FlagPrometheusIncrementalQueryInstrumentation = "prometheusIncrementalQueryInstrumentation"
// FlagLogsExploreTableVisualisation // FlagLogsExploreTableVisualisation
// A table visualisation for logs in Explore // A table visualisation for logs in Explore
FlagLogsExploreTableVisualisation = "logsExploreTableVisualisation" FlagLogsExploreTableVisualisation = "logsExploreTableVisualisation"
@ -655,10 +651,6 @@ const (
// Enables Query Library feature in Explore // Enables Query Library feature in Explore
FlagQueryLibrary = "queryLibrary" FlagQueryLibrary = "queryLibrary"
// FlagAutofixDSUID
// Automatically migrates invalid datasource UIDs
FlagAutofixDSUID = "autofixDSUID"
// FlagLogsExploreTableDefaultVisualization // FlagLogsExploreTableDefaultVisualization
// Sets the logs table as default visualisation in logs explore // Sets the logs table as default visualisation in logs explore
FlagLogsExploreTableDefaultVisualization = "logsExploreTableDefaultVisualization" FlagLogsExploreTableDefaultVisualization = "logsExploreTableDefaultVisualization"
@ -719,6 +711,10 @@ const (
// Use the new SSO Settings API to configure LDAP // Use the new SSO Settings API to configure LDAP
FlagSsoSettingsLDAP = "ssoSettingsLDAP" FlagSsoSettingsLDAP = "ssoSettingsLDAP"
// FlagFailWrongDSUID
// Throws an error if a datasource has an invalid UIDs
FlagFailWrongDSUID = "failWrongDSUID"
// FlagDatabaseReadReplica // FlagDatabaseReadReplica
// Use a read replica for some database queries. // Use a read replica for some database queries.
FlagDatabaseReadReplica = "databaseReadReplica" FlagDatabaseReadReplica = "databaseReadReplica"

@ -401,8 +401,9 @@
{ {
"metadata": { "metadata": {
"name": "autofixDSUID", "name": "autofixDSUID",
"resourceVersion": "1718727528075", "resourceVersion": "1717578796182",
"creationTimestamp": "2024-05-03T11:32:07Z" "creationTimestamp": "2024-05-03T11:32:07Z",
"deletionTimestamp": "2024-06-18T14:28:32Z"
}, },
"spec": { "spec": {
"description": "Automatically migrates invalid datasource UIDs", "description": "Automatically migrates invalid datasource UIDs",
@ -915,6 +916,18 @@
"frontend": true "frontend": true
} }
}, },
{
"metadata": {
"name": "failWrongDSUID",
"resourceVersion": "1718721033692",
"creationTimestamp": "2024-06-18T14:30:33Z"
},
"spec": {
"description": "Throws an error if a datasource has an invalid UIDs",
"stage": "experimental",
"codeowner": "@grafana/plugins-platform-backend"
}
},
{ {
"metadata": { "metadata": {
"name": "faroDatasourceSelector", "name": "faroDatasourceSelector",
@ -1819,7 +1832,8 @@
"metadata": { "metadata": {
"name": "prometheusIncrementalQueryInstrumentation", "name": "prometheusIncrementalQueryInstrumentation",
"resourceVersion": "1718727528075", "resourceVersion": "1718727528075",
"creationTimestamp": "2023-07-05T19:39:49Z" "creationTimestamp": "2023-07-05T19:39:49Z",
"deletionTimestamp": "2024-06-20T11:30:37Z"
}, },
"spec": { "spec": {
"description": "Adds RudderStack events to incremental queries", "description": "Adds RudderStack events to incremental queries",

@ -5,7 +5,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
) )
@ -22,8 +21,8 @@ type FakeRuleService struct {
AuthorizeDatasourceAccessForRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error AuthorizeDatasourceAccessForRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error
HasAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) (bool, error) HasAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) (bool, error)
AuthorizeAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error AuthorizeAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error
HasAccessInFolderFunc func(context.Context, identity.Requester, accesscontrol.Namespaced) (bool, error) HasAccessInFolderFunc func(context.Context, identity.Requester, models.Namespaced) (bool, error)
AuthorizeAccessInFolderFunc func(context.Context, identity.Requester, accesscontrol.Namespaced) error AuthorizeAccessInFolderFunc func(context.Context, identity.Requester, models.Namespaced) error
AuthorizeRuleChangesFunc func(context.Context, identity.Requester, *store.GroupDelta) error AuthorizeRuleChangesFunc func(context.Context, identity.Requester, *store.GroupDelta) error
Calls []Call Calls []Call
@ -77,7 +76,7 @@ func (s *FakeRuleService) AuthorizeAccessToRuleGroup(ctx context.Context, user i
return nil return nil
} }
func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) (bool, error) { func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) (bool, error) {
s.Calls = append(s.Calls, Call{"HasAccessInFolder", []interface{}{ctx, user, namespaced}}) s.Calls = append(s.Calls, Call{"HasAccessInFolder", []interface{}{ctx, user, namespaced}})
if s.HasAccessInFolderFunc != nil { if s.HasAccessInFolderFunc != nil {
return s.HasAccessInFolderFunc(ctx, user, namespaced) return s.HasAccessInFolderFunc(ctx, user, namespaced)
@ -85,7 +84,7 @@ func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.R
return false, nil return false, nil
} }
func (s *FakeRuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error { func (s *FakeRuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
s.Calls = append(s.Calls, Call{"AuthorizeAccessInFolder", []interface{}{ctx, user, namespaced}}) s.Calls = append(s.Calls, Call{"AuthorizeAccessInFolder", []interface{}{ctx, user, namespaced}})
if s.AuthorizeAccessInFolderFunc != nil { if s.AuthorizeAccessInFolderFunc != nil {
return s.AuthorizeAccessInFolderFunc(ctx, user, namespaced) return s.AuthorizeAccessInFolderFunc(ctx, user, namespaced)

@ -31,10 +31,6 @@ func NewRuleService(ac accesscontrol.AccessControl) *RuleService {
} }
} }
type Namespaced interface {
GetNamespaceUID() string
}
// getReadFolderAccessEvaluator constructs accesscontrol.Evaluator that checks all permissions required to read rules in specific folder // getReadFolderAccessEvaluator constructs accesscontrol.Evaluator that checks all permissions required to read rules in specific folder
func getReadFolderAccessEvaluator(folderUID string) accesscontrol.Evaluator { func getReadFolderAccessEvaluator(folderUID string) accesscontrol.Evaluator {
return accesscontrol.EvalAll( return accesscontrol.EvalAll(
@ -130,7 +126,7 @@ func (r *RuleService) AuthorizeAccessToRuleGroup(ctx context.Context, user ident
// - ("folders:read") read the folder // - ("folders:read") read the folder
// - ("alert.rules:read") read alert rules in the folder // - ("alert.rules:read") read alert rules in the folder
// Returns false if the requester does not have enough permissions, and error if something went wrong during the permission evaluation. // Returns false if the requester does not have enough permissions, and error if something went wrong during the permission evaluation.
func (r *RuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, rule Namespaced) (bool, error) { func (r *RuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, rule models.Namespaced) (bool, error) {
eval := accesscontrol.EvalAll(getReadFolderAccessEvaluator(rule.GetNamespaceUID())) eval := accesscontrol.EvalAll(getReadFolderAccessEvaluator(rule.GetNamespaceUID()))
return r.HasAccess(ctx, user, eval) return r.HasAccess(ctx, user, eval)
} }
@ -140,7 +136,7 @@ func (r *RuleService) HasAccessInFolder(ctx context.Context, user identity.Reque
// - ("folders:read") read the folder // - ("folders:read") read the folder
// - ("alert.rules:read") read alert rules in the folder // - ("alert.rules:read") read alert rules in the folder
// Returns error if at least one permission is missing or if something went wrong during the permission evaluation // Returns error if at least one permission is missing or if something went wrong during the permission evaluation
func (r *RuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, rule Namespaced) error { func (r *RuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, rule models.Namespaced) error {
eval := accesscontrol.EvalAll(getReadFolderAccessEvaluator(rule.GetNamespaceUID())) eval := accesscontrol.EvalAll(getReadFolderAccessEvaluator(rule.GetNamespaceUID()))
return r.HasAccessOrError(ctx, user, eval, func() string { return r.HasAccessOrError(ctx, user, eval, func() string {
return fmt.Sprintf("access rules in folder '%s'", rule.GetNamespaceUID()) return fmt.Sprintf("access rules in folder '%s'", rule.GetNamespaceUID())

@ -45,7 +45,7 @@ type RuleAccessControlService interface {
AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
AuthorizeDatasourceAccessForRule(ctx context.Context, user identity.Requester, rule *models.AlertRule) error AuthorizeDatasourceAccessForRule(ctx context.Context, user identity.Requester, rule *models.AlertRule) error
AuthorizeDatasourceAccessForRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error AuthorizeDatasourceAccessForRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error
} }
// API handlers. // API handlers.

@ -11,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/services/ngalert/state"
@ -147,7 +146,7 @@ func (f fakeRuleAccessControlService) AuthorizeAccessToRuleGroup(ctx context.Con
return nil return nil
} }
func (f fakeRuleAccessControlService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error { func (f fakeRuleAccessControlService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
return nil return nil
} }

@ -269,6 +269,11 @@ type AlertRule struct {
NotificationSettings []NotificationSettings `xorm:"notification_settings"` // we use slice to workaround xorm mapping that does not serialize a struct to JSON unless it's a slice NotificationSettings []NotificationSettings `xorm:"notification_settings"` // we use slice to workaround xorm mapping that does not serialize a struct to JSON unless it's a slice
} }
// Namespaced describes a class of resources that are stored in a specific namespace.
type Namespaced interface {
GetNamespaceUID() string
}
// AlertRuleWithOptionals This is to avoid having to pass in additional arguments deep in the call stack. Alert rule // AlertRuleWithOptionals This is to avoid having to pass in additional arguments deep in the call stack. Alert rule
// object is created in an early validation step without knowledge about current alert rule fields or if they need to be // object is created in an early validation step without knowledge about current alert rule fields or if they need to be
// overridden. This is done in a later step and, in that step, we did not have knowledge about if a field was optional // overridden. This is done in a later step and, in that step, we did not have knowledge about if a field was optional

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
) )
@ -24,7 +23,7 @@ type SilenceService struct {
} }
type RuleAccessControlService interface { type RuleAccessControlService interface {
HasAccessInFolder(ctx context.Context, user identity.Requester, rule accesscontrol.Namespaced) (bool, error) HasAccessInFolder(ctx context.Context, user identity.Requester, rule models.Namespaced) (bool, error)
} }
// SilenceAccessControlService provides access control for silences. // SilenceAccessControlService provides access control for silences.

@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes" "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -61,7 +60,7 @@ func TestWithRuleMetadata(t *testing.T) {
user := ac.BackgroundUser("test", 1, org.RoleNone, nil) user := ac.BackgroundUser("test", 1, org.RoleNone, nil)
t.Run("Attach rule metadata to silences", func(t *testing.T) { t.Run("Attach rule metadata to silences", func(t *testing.T) {
ruleAuthz := fakes.FakeRuleService{} ruleAuthz := fakes.FakeRuleService{}
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence accesscontrol.Namespaced) (bool, error) { ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence models.Namespaced) (bool, error) {
return true, nil return true, nil
} }
@ -95,7 +94,7 @@ func TestWithRuleMetadata(t *testing.T) {
}) })
t.Run("Don't attach full rule metadata if no access or global", func(t *testing.T) { t.Run("Don't attach full rule metadata if no access or global", func(t *testing.T) {
ruleAuthz := fakes.FakeRuleService{} ruleAuthz := fakes.FakeRuleService{}
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence accesscontrol.Namespaced) (bool, error) { ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence models.Namespaced) (bool, error) {
return silence.GetNamespaceUID() == "folder1", nil return silence.GetNamespaceUID() == "folder1", nil
} }
@ -134,7 +133,7 @@ func TestWithRuleMetadata(t *testing.T) {
}) })
t.Run("Don't check same namespace access more than once", func(t *testing.T) { t.Run("Don't check same namespace access more than once", func(t *testing.T) {
ruleAuthz := fakes.FakeRuleService{} ruleAuthz := fakes.FakeRuleService{}
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence accesscontrol.Namespaced) (bool, error) { ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence models.Namespaced) (bool, error) {
return true, nil return true, nil
} }
@ -159,7 +158,7 @@ func TestWithRuleMetadata(t *testing.T) {
require.NoError(t, svc.WithRuleMetadata(context.Background(), user, silencesWithMetadata...)) require.NoError(t, svc.WithRuleMetadata(context.Background(), user, silencesWithMetadata...))
assert.Lenf(t, ruleAuthz.Calls, 1, "HasAccessInFolder should be called only once per namespace") assert.Lenf(t, ruleAuthz.Calls, 1, "HasAccessInFolder should be called only once per namespace")
assert.Equal(t, "HasAccessInFolder", ruleAuthz.Calls[0].MethodName) assert.Equal(t, "HasAccessInFolder", ruleAuthz.Calls[0].MethodName)
assert.Equal(t, "folder1", ruleAuthz.Calls[0].Arguments[2].(accesscontrol.Namespaced).GetNamespaceUID()) assert.Equal(t, "folder1", ruleAuthz.Calls[0].Arguments[2].(models.Namespaced).GetNamespaceUID())
}) })
} }

@ -5,7 +5,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
) )
@ -13,7 +12,7 @@ import (
type RuleAccessControlService interface { type RuleAccessControlService interface {
HasAccess(ctx context.Context, user identity.Requester, evaluator ac.Evaluator) (bool, error) HasAccess(ctx context.Context, user identity.Requester, evaluator ac.Evaluator) (bool, error)
AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error
AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
} }

@ -11,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
accesscontrol2 "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes" "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -200,7 +199,7 @@ func TestAuthorizeAccessToRule(t *testing.T) {
rs.HasAccessFunc = func(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { rs.HasAccessFunc = func(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
return false, nil return false, nil
} }
rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, namespaced accesscontrol2.Namespaced) error { rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, namespaced models.Namespaced) error {
return nil return nil
} }
@ -231,7 +230,7 @@ func TestAuthorizeAccessToRule(t *testing.T) {
return false, nil return false, nil
} }
expected = errors.New("test2") expected = errors.New("test2")
rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, rule accesscontrol2.Namespaced) error { rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, rule models.Namespaced) error {
return expected return expected
} }

@ -1020,7 +1020,7 @@ func TestGetAlertRule(t *testing.T) {
service, _, _, ac := initServiceWithData(t) service, _, _, ac := initServiceWithData(t)
expected := errors.New("test") expected := errors.New("test")
ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error { ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
assert.Equal(t, u, user) assert.Equal(t, u, user)
assert.EqualValues(t, rule, namespaced) assert.EqualValues(t, rule, namespaced)
return expected return expected
@ -1034,7 +1034,7 @@ func TestGetAlertRule(t *testing.T) {
assert.Equal(t, "AuthorizeRuleRead", ac.Calls[0].Method) assert.Equal(t, "AuthorizeRuleRead", ac.Calls[0].Method)
ac.Calls = nil ac.Calls = nil
ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error { ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
return nil return nil
} }

@ -9,7 +9,6 @@ import (
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -161,7 +160,7 @@ type fakeRuleAccessControlService struct {
mu sync.Mutex mu sync.Mutex
Calls []call Calls []call
AuthorizeAccessToRuleGroupFunc func(ctx context.Context, user identity.Requester, rules models.RulesGroup) error AuthorizeAccessToRuleGroupFunc func(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
AuthorizeAccessInFolderFunc func(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error AuthorizeAccessInFolderFunc func(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error
AuthorizeRuleChangesFunc func(ctx context.Context, user identity.Requester, change *store.GroupDelta) error AuthorizeRuleChangesFunc func(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
CanReadAllRulesFunc func(ctx context.Context, user identity.Requester) (bool, error) CanReadAllRulesFunc func(ctx context.Context, user identity.Requester) (bool, error)
CanWriteAllRulesFunc func(ctx context.Context, user identity.Requester) (bool, error) CanWriteAllRulesFunc func(ctx context.Context, user identity.Requester) (bool, error)

@ -109,7 +109,7 @@ type broadcaster[T any] struct {
// subscription management // subscription management
cache Cache[T] cache channelCache[T]
subscribe chan chan T subscribe chan chan T
unsubscribe chan (<-chan T) unsubscribe chan (<-chan T)
subs map[<-chan T]chan T subs map[<-chan T]chan T
@ -166,7 +166,7 @@ func (b *broadcaster[T]) init(ctx context.Context, connect ConnectFunc[T]) error
// initialize our internal state // initialize our internal state
b.shouldTerminate = ctx.Done() b.shouldTerminate = ctx.Done()
b.cache = NewCache[T](ctx, 100) b.cache = newChannelCache[T](ctx, 100)
b.subscribe = make(chan chan T, 100) b.subscribe = make(chan chan T, 100)
b.unsubscribe = make(chan (<-chan T), 100) b.unsubscribe = make(chan (<-chan T), 100)
b.subs = make(map[<-chan T]chan T) b.subs = make(map[<-chan T]chan T)
@ -239,9 +239,9 @@ func (b *broadcaster[T]) stream(input <-chan T) {
} }
} }
const DefaultCacheSize = 100 const defaultCacheSize = 100
type Cache[T any] interface { type channelCache[T any] interface {
Len() int Len() int
Add(item T) Add(item T)
Get(i int) T Get(i int) T
@ -260,12 +260,12 @@ type cache[T any] struct {
ctx context.Context ctx context.Context
} }
func NewCache[T any](ctx context.Context, size int) Cache[T] { func newChannelCache[T any](ctx context.Context, size int) channelCache[T] {
c := &cache[T]{} c := &cache[T]{}
c.ctx = ctx c.ctx = ctx
if size <= 0 { if size <= 0 {
size = DefaultCacheSize size = defaultCacheSize
} }
c.size = size c.size = size
c.cache = make([]T, c.size) c.cache = make([]T, c.size)

@ -8,7 +8,7 @@ import (
) )
func TestCache(t *testing.T) { func TestCache(t *testing.T) {
c := NewCache[int](context.Background(), 10) c := newChannelCache[int](context.Background(), 10)
e := []int{} e := []int{}
err := c.Range(func(i int) error { err := c.Range(func(i int) error {

@ -1,92 +0,0 @@
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI=
cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag=
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg=
github.com/aws/aws-sdk-go v1.51.31 h1:4TM+sNc+Dzs7wY1sJ0+J8i60c6rkgnKP1pvPx8ghsSY=
github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU=
github.com/aws/aws-sdk-go-v2/config v1.15.3 h1:5AlQD0jhVXlGzwo+VORKiUuogkG7pQcLJNzIzK7eodw=
github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3 h1:ir7iEq78s4txFGgwcLqD6q9IIPzTQNRJXulJd9h/zQo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 h1:I0dcwWitE752hVSMrsLCxqNQ+UdEp3nACx2bYNMQq+k=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 h1:BKjwCJPnANbkwQ8vzSbaZDKawwagDubrH/z/c0X+kbQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3 h1:rMPtwA7zzkSQZhhz9U3/SoIDz/NZ7Q+iRn4EIO8rSyU=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q=
github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240613114114-5e2f08de316d h1:/UE5JdF+0hxll7EuuO7zRzAxXrvAxQo5M9eqOepc2mQ=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=

@ -32,7 +32,6 @@ var mtx sync.Mutex
// Legacy UID pattern // Legacy UID pattern
var validUIDCharPattern = `a-zA-Z0-9\-\_` var validUIDCharPattern = `a-zA-Z0-9\-\_`
var validUIDPattern = regexp.MustCompile(`^[` + validUIDCharPattern + `]*$`).MatchString var validUIDPattern = regexp.MustCompile(`^[` + validUIDCharPattern + `]*$`).MatchString
var validUIDReplacer = regexp.MustCompile(`[^` + validUIDCharPattern + `]`).ReplaceAllString
// IsValidShortUID checks if short unique identifier contains valid characters // IsValidShortUID checks if short unique identifier contains valid characters
// NOTE: future Grafana UIDs will need conform to https://github.com/kubernetes/apimachinery/blob/master/pkg/util/validation/validation.go#L43 // NOTE: future Grafana UIDs will need conform to https://github.com/kubernetes/apimachinery/blob/master/pkg/util/validation/validation.go#L43
@ -93,13 +92,3 @@ func ValidateUID(uid string) error {
} }
return nil return nil
} }
func AutofixUID(uid string) string {
if IsShortUIDTooLong(uid) {
return uid[:MaxUIDLength]
}
if !IsValidShortUID(uid) {
uid = validUIDReplacer(uid, "-")
}
return uid
}

@ -143,32 +143,3 @@ func TestValidateUID(t *testing.T) {
}) })
} }
} }
func TestAutofixUID(t *testing.T) {
var tests = []struct {
name string
uid string
expected string
}{
{
name: "return input when input is valid",
uid: "f8cc010c-ee72-4681-89d2-d46e1bd47d33",
expected: "f8cc010c-ee72-4681-89d2-d46e1bd47d33",
},
{
name: "generate new uid when input is too long",
uid: strings.Repeat("1", MaxUIDLength+1),
expected: strings.Repeat("1", MaxUIDLength),
},
{
name: "generate new uid when input has invalid characters",
uid: "f8cc010c.ee72.4681;89d2+d46e1bd47d33",
expected: "f8cc010c-ee72-4681-89d2-d46e1bd47d33",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, AutofixUID(tt.uid))
})
}
}

@ -21,6 +21,7 @@ import { ScopesScene } from './ScopesScene';
import { ScopesTreeLevel } from './ScopesTreeLevel'; import { ScopesTreeLevel } from './ScopesTreeLevel';
import { fetchNodes, fetchScope, fetchSelectedScopes } from './api'; import { fetchNodes, fetchScope, fetchSelectedScopes } from './api';
import { NodesMap, SelectedScope, TreeScope } from './types'; import { NodesMap, SelectedScope, TreeScope } from './types';
import { getBasicScope } from './utils';
export interface ScopesFiltersSceneState extends SceneObjectState { export interface ScopesFiltersSceneState extends SceneObjectState {
nodes: NodesMap; nodes: NodesMap;
@ -180,9 +181,16 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
return; return;
} }
this.setState({ treeScopes, isLoadingScopes: true }); this.setState({
// Update the scopes with the basic scopes otherwise they'd be lost between URL syncs
scopes: treeScopes.map(({ scopeName, path }) => ({ scope: getBasicScope(scopeName), path })),
treeScopes,
isLoadingScopes: true,
});
const scopes = await fetchSelectedScopes(treeScopes);
this.setState({ scopes: await fetchSelectedScopes(treeScopes), isLoadingScopes: false }); this.setState({ scopes, isLoadingScopes: false });
} }
public resetDirtyScopeNames() { public resetDirtyScopeNames() {

@ -30,7 +30,7 @@ export class ScopesScene extends SceneObjectBase<ScopesSceneState> {
this.addActivationHandler(() => { this.addActivationHandler(() => {
this._subs.add( this._subs.add(
this.state.filters.subscribeToState((newState, prevState) => { this.state.filters.subscribeToState((newState, prevState) => {
if (newState.scopes !== prevState.scopes) { if (!newState.isLoadingScopes && newState.scopes !== prevState.scopes) {
if (this.state.isExpanded) { if (this.state.isExpanded) {
this.state.dashboards.fetchDashboards(this.state.filters.getSelectedScopes()); this.state.dashboards.fetchDashboards(this.state.filters.getSelectedScopes());
} }

@ -3,6 +3,7 @@ import { config, getBackendSrv } from '@grafana/runtime';
import { ScopedResourceClient } from 'app/features/apiserver/client'; import { ScopedResourceClient } from 'app/features/apiserver/client';
import { NodesMap, SelectedScope, SuggestedDashboard, TreeScope } from './types'; import { NodesMap, SelectedScope, SuggestedDashboard, TreeScope } from './types';
import { getBasicScope, mergeScopes } from './utils';
const group = 'scope.grafana.app'; const group = 'scope.grafana.app';
const version = 'v0alpha1'; const version = 'v0alpha1';
@ -48,31 +49,12 @@ export async function fetchScope(name: string): Promise<Scope> {
} }
const response = new Promise<Scope>(async (resolve) => { const response = new Promise<Scope>(async (resolve) => {
const basicScope: Scope = { const basicScope = getBasicScope(name);
metadata: { name },
spec: {
filters: [],
title: name,
type: '',
category: '',
description: '',
},
};
try { try {
const serverScope = await scopesClient.get(name); const serverScope = await scopesClient.get(name);
const scope = { const scope = mergeScopes(basicScope, serverScope);
...basicScope,
metadata: {
...basicScope.metadata,
...serverScope.metadata,
},
spec: {
...basicScope.spec,
...serverScope.spec,
},
};
resolve(scope); resolve(scope);
} catch (err) { } catch (err) {

@ -0,0 +1,28 @@
import { Scope } from '@grafana/data';
export function getBasicScope(name: string): Scope {
return {
metadata: { name },
spec: {
filters: [],
title: name,
type: '',
category: '',
description: '',
},
};
}
export function mergeScopes(scope1: Scope, scope2: Scope): Scope {
return {
...scope1,
metadata: {
...scope1.metadata,
...scope2.metadata,
},
spec: {
...scope1.spec,
...scope2.spec,
},
};
}

@ -16,7 +16,10 @@ export interface ContentOutlineContextProps {
outlineItems: ContentOutlineItemContextProps[]; outlineItems: ContentOutlineItemContextProps[];
register: RegisterFunction; register: RegisterFunction;
unregister: (id: string) => void; unregister: (id: string) => void;
unregisterAllChildren: (parentId: string, childType: ITEM_TYPES) => void; unregisterAllChildren: (
parentIdGetter: (items: ContentOutlineItemContextProps[]) => string | undefined,
childType: ITEM_TYPES
) => void;
updateOutlineItems: (newItems: ContentOutlineItemContextProps[]) => void; updateOutlineItems: (newItems: ContentOutlineItemContextProps[]) => void;
updateItem: (id: string, properties: Partial<Omit<ContentOutlineItemContextProps, 'id'>>) => void; updateItem: (id: string, properties: Partial<Omit<ContentOutlineItemContextProps, 'id'>>) => void;
} }
@ -193,16 +196,23 @@ export function ContentOutlineContextProvider({ children, refreshDependencies }:
); );
}, []); }, []);
const unregisterAllChildren = useCallback((parentId: string, childType: ITEM_TYPES) => { const unregisterAllChildren = useCallback(
setOutlineItems((prevItems) => (parentIdGetter: (items: ContentOutlineItemContextProps[]) => string | undefined, childType: ITEM_TYPES) => {
prevItems.map((item) => { setOutlineItems((prevItems) => {
if (item.id === parentId) { const parentId = parentIdGetter(prevItems);
item.children = item.children?.filter((child) => child.type !== childType); if (!parentId) {
return prevItems;
} }
return item; return prevItems.map((item) => {
}) if (item.id === parentId) {
); item.children = item.children?.filter((child) => child.type !== childType);
}, []); }
return item;
});
});
},
[]
);
useEffect(() => { useEffect(() => {
setOutlineItems((prevItems) => { setOutlineItems((prevItems) => {

@ -1,5 +1,5 @@
import { identity } from 'lodash'; import { identity } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { usePrevious } from 'react-use'; import { usePrevious } from 'react-use';
import { import {
@ -154,8 +154,10 @@ export function ExploreGraph({
const structureRev = useStructureRev(dataWithConfig); const structureRev = useStructureRev(dataWithConfig);
const onHiddenSeriesChangedRef = useRef(onHiddenSeriesChanged);
useEffect(() => { useEffect(() => {
if (onHiddenSeriesChanged) { if (onHiddenSeriesChangedRef.current) {
const hiddenFrames: string[] = []; const hiddenFrames: string[] = [];
dataWithConfig.forEach((frame) => { dataWithConfig.forEach((frame) => {
const allFieldsHidden = frame.fields.map((field) => field.config?.custom?.hideFrom?.viz).every(identity); const allFieldsHidden = frame.fields.map((field) => field.config?.custom?.hideFrom?.viz).every(identity);
@ -163,9 +165,9 @@ export function ExploreGraph({
hiddenFrames.push(getFrameDisplayName(frame)); hiddenFrames.push(getFrameDisplayName(frame));
} }
}); });
onHiddenSeriesChanged(hiddenFrames); onHiddenSeriesChangedRef.current(hiddenFrames);
} }
}, [dataWithConfig, onHiddenSeriesChanged]); }, [dataWithConfig]);
const panelContext: PanelContext = { const panelContext: PanelContext = {
eventsScope: 'explore', eventsScope: 'explore',

@ -1,11 +1,11 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import React, { ComponentProps } from 'react'; import React, { ComponentProps } from 'react';
import { Provider } from 'react-redux';
import { import {
DataFrame, DataFrame,
EventBusSrv, EventBusSrv,
ExploreLogsPanelState,
ExplorePanelsState, ExplorePanelsState,
LoadingState, LoadingState,
LogLevel, LogLevel,
@ -13,25 +13,20 @@ import {
standardTransformersRegistry, standardTransformersRegistry,
toUtc, toUtc,
createDataFrame, createDataFrame,
ExploreLogsPanelState,
} from '@grafana/data'; } from '@grafana/data';
import { organizeFieldsTransformer } from '@grafana/data/src/transformations/transformers/organize'; import { organizeFieldsTransformer } from '@grafana/data/src/transformations/transformers/organize';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import store from 'app/core/store';
import { extractFieldsTransformer } from 'app/features/transformers/extractFields/extractFields'; import { extractFieldsTransformer } from 'app/features/transformers/extractFields/extractFields';
import { configureStore } from 'app/store/configureStore';
import { initialExploreState } from '../state/main';
import { makeExplorePaneState } from '../state/utils';
import { Logs } from './Logs'; import { Logs } from './Logs';
import { visualisationTypeKey } from './utils/logs'; import { visualisationTypeKey } from './utils/logs';
import { getMockElasticFrame, getMockLokiFrame } from './utils/testMocks.test'; import { getMockElasticFrame, getMockLokiFrame } from './utils/testMocks.test';
jest.mock('app/core/store', () => {
return {
getBool: jest.fn(),
getObject: jest.fn((_a, b) => b),
get: jest.fn(),
set: jest.fn(),
};
});
const reportInteraction = jest.fn(); const reportInteraction = jest.fn();
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
@ -45,26 +40,11 @@ jest.mock('app/core/utils/shortLinks', () => ({
createAndCopyShortLink: (url: string) => createAndCopyShortLink(url), createAndCopyShortLink: (url: string) => createAndCopyShortLink(url),
})); }));
jest.mock('app/store/store', () => ({ const fakeChangePanelState = jest.fn().mockReturnValue({ type: 'fakeAction' });
getState: jest.fn().mockReturnValue({
explore: {
panes: {
left: {
datasource: 'id',
queries: [{ refId: 'A', expr: '', queryType: 'range', datasource: { type: 'loki', uid: 'id' } }],
range: { raw: { from: 'now-1h', to: 'now' } },
},
},
},
}),
dispatch: jest.fn(),
}));
const changePanelState = jest.fn();
jest.mock('../state/explorePane', () => ({ jest.mock('../state/explorePane', () => ({
...jest.requireActual('../state/explorePane'), ...jest.requireActual('../state/explorePane'),
changePanelState: (exploreId: string, panel: 'logs', panelState: {} | ExploreLogsPanelState) => { changePanelState: (exploreId: string, panel: 'logs', panelState: {} | ExploreLogsPanelState) => {
return changePanelState(exploreId, panel, panelState); return fakeChangePanelState(exploreId, panel, panelState);
}, },
})); }));
@ -72,6 +52,7 @@ describe('Logs', () => {
let originalHref = window.location.href; let originalHref = window.location.href;
beforeEach(() => { beforeEach(() => {
localStorage.clear();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@ -120,6 +101,7 @@ describe('Logs', () => {
]; ];
const testDataFrame = dataFrame ?? getMockLokiFrame(); const testDataFrame = dataFrame ?? getMockLokiFrame();
return ( return (
<Logs <Logs
exploreId={'left'} exploreId={'left'}
@ -157,8 +139,23 @@ describe('Logs', () => {
/> />
); );
}; };
const setup = (partialProps?: Partial<ComponentProps<typeof Logs>>, dataFrame?: DataFrame, logs?: LogRowModel[]) => { const setup = (partialProps?: Partial<ComponentProps<typeof Logs>>, dataFrame?: DataFrame, logs?: LogRowModel[]) => {
return render(getComponent(partialProps, dataFrame ? dataFrame : getMockLokiFrame(), logs)); const fakeStore = configureStore({
explore: {
...initialExploreState,
panes: {
left: makeExplorePaneState(),
},
},
});
const { rerender } = render(
<Provider store={fakeStore}>
{getComponent(partialProps, dataFrame ? dataFrame : getMockLokiFrame(), logs)}
</Provider>
);
return { rerender, store: fakeStore };
}; };
describe('scrolling behavior', () => { describe('scrolling behavior', () => {
@ -216,40 +213,47 @@ describe('Logs', () => {
it('should render a load more button', () => { it('should render a load more button', () => {
const scanningStarted = jest.fn(); const scanningStarted = jest.fn();
const store = configureStore({
explore: {
...initialExploreState,
},
});
render( render(
<Logs <Provider store={store}>
exploreId={'left'} <Logs
splitOpen={() => undefined} exploreId={'left'}
logsVolumeEnabled={true} splitOpen={() => undefined}
onSetLogsVolumeEnabled={() => null} logsVolumeEnabled={true}
onClickFilterLabel={() => null} onSetLogsVolumeEnabled={() => null}
onClickFilterOutLabel={() => null} onClickFilterLabel={() => null}
logsVolumeData={undefined} onClickFilterOutLabel={() => null}
loadLogsVolumeData={() => undefined} logsVolumeData={undefined}
logRows={[]} loadLogsVolumeData={() => undefined}
onStartScanning={scanningStarted} logRows={[]}
timeZone={'utc'} onStartScanning={scanningStarted}
width={50} timeZone={'utc'}
loading={false} width={50}
loadingState={LoadingState.Done} loading={false}
absoluteRange={{ loadingState={LoadingState.Done}
from: toUtc('2019-01-01 10:00:00').valueOf(), absoluteRange={{
to: toUtc('2019-01-01 16:00:00').valueOf(), from: toUtc('2019-01-01 10:00:00').valueOf(),
}} to: toUtc('2019-01-01 16:00:00').valueOf(),
range={{ }}
from: toUtc('2019-01-01 10:00:00'), range={{
to: toUtc('2019-01-01 16:00:00'), from: toUtc('2019-01-01 10:00:00'),
raw: { from: 'now-1h', to: 'now' }, to: toUtc('2019-01-01 16:00:00'),
}} raw: { from: 'now-1h', to: 'now' },
addResultsToCache={() => {}} }}
onChangeTime={() => {}} addResultsToCache={() => {}}
clearCache={() => {}} onChangeTime={() => {}}
getFieldLinks={() => { clearCache={() => {}}
return []; getFieldLinks={() => {
}} return [];
eventBus={new EventBusSrv()} }}
isFilterLabelActive={jest.fn()} eventBus={new EventBusSrv()}
/> isFilterLabelActive={jest.fn()}
/>
</Provider>
); );
const button = screen.getByRole('button', { const button = screen.getByRole('button', {
name: /scan for older logs/i, name: /scan for older logs/i,
@ -259,40 +263,47 @@ describe('Logs', () => {
}); });
it('should render a stop scanning button', () => { it('should render a stop scanning button', () => {
const store = configureStore({
explore: {
...initialExploreState,
},
});
render( render(
<Logs <Provider store={store}>
exploreId={'left'} <Logs
splitOpen={() => undefined} exploreId={'left'}
logsVolumeEnabled={true} splitOpen={() => undefined}
onSetLogsVolumeEnabled={() => null} logsVolumeEnabled={true}
onClickFilterLabel={() => null} onSetLogsVolumeEnabled={() => null}
onClickFilterOutLabel={() => null} onClickFilterLabel={() => null}
logsVolumeData={undefined} onClickFilterOutLabel={() => null}
loadLogsVolumeData={() => undefined} logsVolumeData={undefined}
logRows={[]} loadLogsVolumeData={() => undefined}
scanning={true} logRows={[]}
timeZone={'utc'} scanning={true}
width={50} timeZone={'utc'}
loading={false} width={50}
loadingState={LoadingState.Done} loading={false}
absoluteRange={{ loadingState={LoadingState.Done}
from: toUtc('2019-01-01 10:00:00').valueOf(), absoluteRange={{
to: toUtc('2019-01-01 16:00:00').valueOf(), from: toUtc('2019-01-01 10:00:00').valueOf(),
}} to: toUtc('2019-01-01 16:00:00').valueOf(),
range={{ }}
from: toUtc('2019-01-01 10:00:00'), range={{
to: toUtc('2019-01-01 16:00:00'), from: toUtc('2019-01-01 10:00:00'),
raw: { from: 'now-1h', to: 'now' }, to: toUtc('2019-01-01 16:00:00'),
}} raw: { from: 'now-1h', to: 'now' },
addResultsToCache={() => {}} }}
onChangeTime={() => {}} addResultsToCache={() => {}}
clearCache={() => {}} onChangeTime={() => {}}
getFieldLinks={() => { clearCache={() => {}}
return []; getFieldLinks={() => {
}} return [];
eventBus={new EventBusSrv()} }}
isFilterLabelActive={jest.fn()} eventBus={new EventBusSrv()}
/> isFilterLabelActive={jest.fn()}
/>
</Provider>
); );
expect( expect(
@ -304,42 +315,48 @@ describe('Logs', () => {
it('should render a stop scanning button', () => { it('should render a stop scanning button', () => {
const scanningStopped = jest.fn(); const scanningStopped = jest.fn();
const store = configureStore({
explore: {
...initialExploreState,
},
});
render( render(
<Logs <Provider store={store}>
exploreId={'left'} <Logs
splitOpen={() => undefined} exploreId={'left'}
logsVolumeEnabled={true} splitOpen={() => undefined}
onSetLogsVolumeEnabled={() => null} logsVolumeEnabled={true}
onClickFilterLabel={() => null} onSetLogsVolumeEnabled={() => null}
onClickFilterOutLabel={() => null} onClickFilterLabel={() => null}
logsVolumeData={undefined} onClickFilterOutLabel={() => null}
loadLogsVolumeData={() => undefined} logsVolumeData={undefined}
logRows={[]} loadLogsVolumeData={() => undefined}
scanning={true} logRows={[]}
onStopScanning={scanningStopped} scanning={true}
timeZone={'utc'} onStopScanning={scanningStopped}
width={50} timeZone={'utc'}
loading={false} width={50}
loadingState={LoadingState.Done} loading={false}
absoluteRange={{ loadingState={LoadingState.Done}
from: toUtc('2019-01-01 10:00:00').valueOf(), absoluteRange={{
to: toUtc('2019-01-01 16:00:00').valueOf(), from: toUtc('2019-01-01 10:00:00').valueOf(),
}} to: toUtc('2019-01-01 16:00:00').valueOf(),
range={{ }}
from: toUtc('2019-01-01 10:00:00'), range={{
to: toUtc('2019-01-01 16:00:00'), from: toUtc('2019-01-01 10:00:00'),
raw: { from: 'now-1h', to: 'now' }, to: toUtc('2019-01-01 16:00:00'),
}} raw: { from: 'now-1h', to: 'now' },
addResultsToCache={() => {}} }}
onChangeTime={() => {}} addResultsToCache={() => {}}
clearCache={() => {}} onChangeTime={() => {}}
getFieldLinks={() => { clearCache={() => {}}
return []; getFieldLinks={() => {
}} return [];
eventBus={new EventBusSrv()} }}
isFilterLabelActive={jest.fn()} eventBus={new EventBusSrv()}
/> isFilterLabelActive={jest.fn()}
/>
</Provider>
); );
const button = screen.getByRole('button', { const button = screen.getByRole('button', {
@ -363,12 +380,12 @@ describe('Logs', () => {
describe('for permalinking', () => { describe('for permalinking', () => {
it('should dispatch a `changePanelState` event without the id', () => { it('should dispatch a `changePanelState` event without the id', () => {
const panelState = { logs: { id: '1' } }; const panelState = { logs: { id: '1' } };
const { rerender } = setup({ loading: false, panelState }); const { rerender, store } = setup({ loading: false, panelState });
rerender(getComponent({ loading: true, exploreId: 'right', panelState })); rerender(<Provider store={store}>{getComponent({ loading: true, exploreId: 'right', panelState })}</Provider>);
rerender(getComponent({ loading: false, exploreId: 'right', panelState })); rerender(<Provider store={store}>{getComponent({ loading: false, exploreId: 'right', panelState })}</Provider>);
expect(changePanelState).toHaveBeenCalledWith('right', 'logs', { logs: {} }); expect(fakeChangePanelState).toHaveBeenCalledWith('right', 'logs', { logs: {} });
}); });
it('should scroll the scrollElement into view if rows contain id', () => { it('should scroll the scrollElement into view if rows contain id', () => {
@ -491,23 +508,17 @@ describe('Logs', () => {
}); });
it('should use default state from localstorage - table', async () => { it('should use default state from localstorage - table', async () => {
const oldGet = store.get;
store.get = jest.fn().mockReturnValue('table');
localStorage.setItem(visualisationTypeKey, 'table'); localStorage.setItem(visualisationTypeKey, 'table');
setup({}); setup({});
const table = await screen.findByTestId('logRowsTable'); const table = await screen.findByTestId('logRowsTable');
expect(table).toBeInTheDocument(); expect(table).toBeInTheDocument();
store.get = oldGet;
}); });
it('should use default state from localstorage - logs', async () => { it('should use default state from localstorage - logs', async () => {
const oldGet = store.get;
store.get = jest.fn().mockReturnValue('logs');
localStorage.setItem(visualisationTypeKey, 'logs'); localStorage.setItem(visualisationTypeKey, 'logs');
setup({}); setup({});
const table = await screen.findByTestId('logRows'); const table = await screen.findByTestId('logRows');
expect(table).toBeInTheDocument(); expect(table).toBeInTheDocument();
store.get = oldGet;
}); });
it('should change visualisation to table on toggle (elastic)', async () => { it('should change visualisation to table on toggle (elastic)', async () => {

File diff suppressed because it is too large Load Diff

@ -270,7 +270,7 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
}; };
} }
if (initializeExplore.pending.match(action)) { if (initializeExplore?.pending.match(action)) {
const initialPanes = Object.entries(state.panes); const initialPanes = Object.entries(state.panes);
const before = initialPanes.slice(0, action.meta.arg.position); const before = initialPanes.slice(0, action.meta.arg.position);
const after = initialPanes.slice(before.length); const after = initialPanes.slice(before.length);

@ -71,10 +71,7 @@ export const GroupByField = (props: Props) => {
const scopeOptions = Object.values(TraceqlSearchScope).map((t) => ({ label: t, value: t })); const scopeOptions = Object.values(TraceqlSearchScope).map((t) => ({ label: t, value: t }));
return ( return (
<InlineSearchField <InlineSearchField label="Aggregate by" tooltip="Select one or more tags to see the metrics summary.">
label="Aggregate by"
tooltip="Select one or more tags to see the metrics summary. Note: the metrics summary API only considers spans of kind = server."
>
<> <>
{query.groupBy?.map((f, i) => { {query.groupBy?.map((f, i) => {
const tags = getTags(f) const tags = getTags(f)

@ -536,9 +536,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
if (!response.data.summaries) { if (!response.data.summaries) {
return { return {
error: { error: {
message: getErrorMessage( message: getErrorMessage(`No summary data for '${groupBy}'.`),
`No summary data for '${groupBy}'. Note: the metrics summary API only considers spans of kind = server. You can check if the attributes exist by running a TraceQL query like { attr_key = attr_value && kind = server }`
),
}, },
data: emptyResponse, data: emptyResponse,
}; };

@ -58,7 +58,7 @@ describe('MetricsSummary', () => {
"datasourceName": "tempo", "datasourceName": "tempo",
"datasourceUid": "gdev-tempo", "datasourceUid": "gdev-tempo",
"query": { "query": {
"query": "{name="HTTP POST - post" && span.http.status_code=\${__data.fields["span.http.status_code"]} && temperature=\${__data.fields["temperature"]} && kind=server} | by(resource.service.name)", "query": "{name="HTTP POST - post" && span.http.status_code=\${__data.fields["span.http.status_code"]} && temperature=\${__data.fields["temperature"]}} | by(resource.service.name)",
"queryType": "traceql", "queryType": "traceql",
}, },
}, },
@ -83,7 +83,7 @@ describe('MetricsSummary', () => {
"datasourceName": "tempo", "datasourceName": "tempo",
"datasourceUid": "gdev-tempo", "datasourceUid": "gdev-tempo",
"query": { "query": {
"query": "{name="HTTP POST - post" && span.http.status_code=\${__data.fields["span.http.status_code"]} && temperature=\${__data.fields["temperature"]} && kind=server} | by(resource.service.name)", "query": "{name="HTTP POST - post" && span.http.status_code=\${__data.fields["span.http.status_code"]} && temperature=\${__data.fields["temperature"]}} | by(resource.service.name)",
"queryType": "traceql", "queryType": "traceql",
}, },
}, },
@ -99,19 +99,6 @@ describe('MetricsSummary', () => {
38.1, 38.1,
], ],
}, },
{
"config": {
"custom": {
"width": 150,
},
"displayNameFromDS": "Kind",
},
"name": "kind",
"type": "string",
"values": [
"server",
],
},
{ {
"config": { "config": {
"custom": { "custom": {
@ -222,7 +209,6 @@ describe('MetricsSummary', () => {
{ {
"contains_sink": "true", "contains_sink": "true",
"errorPercentage": 10, "errorPercentage": 10,
"kind": "server",
"p50": 1, "p50": 1,
"p90": 2, "p90": 2,
"p95": 3, "p95": 3,
@ -241,21 +227,21 @@ describe('MetricsSummary', () => {
it('getConfigQuery should return correctly for empty target query', () => { it('getConfigQuery should return correctly for empty target query', () => {
const result = getConfigQuery(series, '{}'); const result = getConfigQuery(series, '{}');
expect(result).toEqual( expect(result).toEqual(
'{span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]} && kind=server}' '{span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]}}'
); );
}); });
it('getConfigQuery should return correctly for target query', () => { it('getConfigQuery should return correctly for target query', () => {
const result = getConfigQuery(series, '{name="HTTP POST - post"} | by(resource.service.name)'); const result = getConfigQuery(series, '{name="HTTP POST - post"} | by(resource.service.name)');
expect(result).toEqual( expect(result).toEqual(
'{name="HTTP POST - post" && span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]} && kind=server} | by(resource.service.name)' '{name="HTTP POST - post" && span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]}} | by(resource.service.name)'
); );
}); });
it('getConfigQuery should return correctly for target query without brackets', () => { it('getConfigQuery should return correctly for target query without brackets', () => {
const result = getConfigQuery(series, 'by(resource.service.name)'); const result = getConfigQuery(series, 'by(resource.service.name)');
expect(result).toEqual( expect(result).toEqual(
'{span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]} && kind=server} | by(resource.service.name)' '{span.http.status_code=${__data.fields["span.http.status_code"]} && temperature=${__data.fields["temperature"]} && room="${__data.fields["room"]}" && contains_sink="${__data.fields["contains_sink"]}" && window_open="${__data.fields["window_open"]}" && spanStatus=${__data.fields["spanStatus"]} && spanKind=${__data.fields["spanKind"]}} | by(resource.service.name)'
); );
}); });
}); });

@ -70,11 +70,6 @@ export function createTableFrameFromMetricsSummaryQuery(
refId: 'metrics-summary', refId: 'metrics-summary',
fields: [ fields: [
...Object.values(dynamicMetrics).sort((a, b) => a.name.localeCompare(b.name)), ...Object.values(dynamicMetrics).sort((a, b) => a.name.localeCompare(b.name)),
{
name: 'kind',
type: FieldType.string,
config: { displayNameFromDS: 'Kind', custom: { width: 150 } },
},
{ {
name: 'spanCount', name: 'spanCount',
type: FieldType.number, type: FieldType.number,
@ -113,7 +108,6 @@ export const transformToMetricsData = (data: MetricsSummary) => {
: '0%'; : '0%';
const metricsData: MetricsData = { const metricsData: MetricsData = {
kind: 'server', // so the user knows all results are of kind = server
spanCount: getNumberForMetric(data.spanCount), spanCount: getNumberForMetric(data.spanCount),
errorPercentage, errorPercentage,
p50: getNumberForMetric(data.p50), p50: getNumberForMetric(data.p50),
@ -145,12 +139,12 @@ export const getConfigQuery = (series: Series[], targetQuery: string) => {
configQuery = targetQuery.substring(0, closingBracketIndex); configQuery = targetQuery.substring(0, closingBracketIndex);
if (queryParts.length > 0) { if (queryParts.length > 0) {
configQuery += targetQuery.replace(/\s/g, '').includes('{}') ? '' : ' && '; configQuery += targetQuery.replace(/\s/g, '').includes('{}') ? '' : ' && ';
configQuery += `${queryParts.join(' && ')} && kind=server`; configQuery += `${queryParts.join(' && ')}`;
configQuery += `}`; configQuery += `}`;
} }
configQuery += `${queryAfterClosingBracket}`; configQuery += `${queryAfterClosingBracket}`;
} else { } else {
configQuery = `{${queryParts.join(' && ')} && kind=server} | ${targetQuery}`; configQuery = `{${queryParts.join(' && ')}} | ${targetQuery}`;
} }
return configQuery; return configQuery;

@ -25,6 +25,14 @@
"user": "Nutzer" "user": "Nutzer"
} }
}, },
"alert-labels": {
"button": {
"hide": "",
"show": {
"tooltip": ""
}
}
},
"alert-rule-form": { "alert-rule-form": {
"evaluation-behaviour": { "evaluation-behaviour": {
"description": { "description": {
@ -138,6 +146,16 @@
"text": "" "text": ""
} }
}, },
"central-alert-history": {
"error": "",
"filter": {
"button": {
"clear": ""
},
"label": "",
"placeholder": ""
}
},
"clipboard-button": { "clipboard-button": {
"inline-toast": { "inline-toast": {
"success": "Kopiert" "success": "Kopiert"
@ -1166,7 +1184,7 @@
"public": { "public": {
"title": "Öffentliche Dashboards" "title": "Öffentliche Dashboards"
}, },
"recentlyDeleted": { "recently-deleted": {
"subtitle": "", "subtitle": "",
"title": "" "title": ""
}, },
@ -1615,6 +1633,7 @@
"tree": { "tree": {
"collapse": "", "collapse": "",
"expand": "", "expand": "",
"headline": "",
"search": "" "search": ""
} }
}, },

@ -25,6 +25,14 @@
"user": "Usuario" "user": "Usuario"
} }
}, },
"alert-labels": {
"button": {
"hide": "",
"show": {
"tooltip": ""
}
}
},
"alert-rule-form": { "alert-rule-form": {
"evaluation-behaviour": { "evaluation-behaviour": {
"description": { "description": {
@ -138,6 +146,16 @@
"text": "" "text": ""
} }
}, },
"central-alert-history": {
"error": "",
"filter": {
"button": {
"clear": ""
},
"label": "",
"placeholder": ""
}
},
"clipboard-button": { "clipboard-button": {
"inline-toast": { "inline-toast": {
"success": "Copiado" "success": "Copiado"
@ -1166,7 +1184,7 @@
"public": { "public": {
"title": "Paneles de control públicos" "title": "Paneles de control públicos"
}, },
"recentlyDeleted": { "recently-deleted": {
"subtitle": "", "subtitle": "",
"title": "" "title": ""
}, },
@ -1615,6 +1633,7 @@
"tree": { "tree": {
"collapse": "", "collapse": "",
"expand": "", "expand": "",
"headline": "",
"search": "" "search": ""
} }
}, },

@ -25,6 +25,14 @@
"user": "Utilisateur" "user": "Utilisateur"
} }
}, },
"alert-labels": {
"button": {
"hide": "",
"show": {
"tooltip": ""
}
}
},
"alert-rule-form": { "alert-rule-form": {
"evaluation-behaviour": { "evaluation-behaviour": {
"description": { "description": {
@ -138,6 +146,16 @@
"text": "" "text": ""
} }
}, },
"central-alert-history": {
"error": "",
"filter": {
"button": {
"clear": ""
},
"label": "",
"placeholder": ""
}
},
"clipboard-button": { "clipboard-button": {
"inline-toast": { "inline-toast": {
"success": "Copié" "success": "Copié"
@ -1166,7 +1184,7 @@
"public": { "public": {
"title": "Tableaux de bord publics" "title": "Tableaux de bord publics"
}, },
"recentlyDeleted": { "recently-deleted": {
"subtitle": "", "subtitle": "",
"title": "" "title": ""
}, },
@ -1615,6 +1633,7 @@
"tree": { "tree": {
"collapse": "", "collapse": "",
"expand": "", "expand": "",
"headline": "",
"search": "" "search": ""
} }
}, },

@ -25,6 +25,14 @@
"user": "Usuário" "user": "Usuário"
} }
}, },
"alert-labels": {
"button": {
"hide": "",
"show": {
"tooltip": ""
}
}
},
"alert-rule-form": { "alert-rule-form": {
"evaluation-behaviour": { "evaluation-behaviour": {
"description": { "description": {
@ -138,6 +146,16 @@
"text": "" "text": ""
} }
}, },
"central-alert-history": {
"error": "",
"filter": {
"button": {
"clear": ""
},
"label": "",
"placeholder": ""
}
},
"clipboard-button": { "clipboard-button": {
"inline-toast": { "inline-toast": {
"success": "Copiado" "success": "Copiado"
@ -1166,7 +1184,7 @@
"public": { "public": {
"title": "Painéis de controle públicos" "title": "Painéis de controle públicos"
}, },
"recentlyDeleted": { "recently-deleted": {
"subtitle": "", "subtitle": "",
"title": "" "title": ""
}, },
@ -1615,6 +1633,7 @@
"tree": { "tree": {
"collapse": "", "collapse": "",
"expand": "", "expand": "",
"headline": "",
"search": "" "search": ""
} }
}, },

@ -25,6 +25,14 @@
"user": "用户" "user": "用户"
} }
}, },
"alert-labels": {
"button": {
"hide": "",
"show": {
"tooltip": ""
}
}
},
"alert-rule-form": { "alert-rule-form": {
"evaluation-behaviour": { "evaluation-behaviour": {
"description": { "description": {
@ -133,6 +141,16 @@
"text": "" "text": ""
} }
}, },
"central-alert-history": {
"error": "",
"filter": {
"button": {
"clear": ""
},
"label": "",
"placeholder": ""
}
},
"clipboard-button": { "clipboard-button": {
"inline-toast": { "inline-toast": {
"success": "已复制" "success": "已复制"
@ -1160,7 +1178,7 @@
"public": { "public": {
"title": "公共仪表板" "title": "公共仪表板"
}, },
"recentlyDeleted": { "recently-deleted": {
"subtitle": "", "subtitle": "",
"title": "" "title": ""
}, },
@ -1608,6 +1626,7 @@
"tree": { "tree": {
"collapse": "", "collapse": "",
"expand": "", "expand": "",
"headline": "",
"search": "" "search": ""
} }
}, },

@ -3555,8 +3555,8 @@ __metadata:
linkType: soft linkType: soft
"@grafana/scenes@npm:^5.0.2": "@grafana/scenes@npm:^5.0.2":
version: 5.0.3 version: 5.1.2
resolution: "@grafana/scenes@npm:5.0.3" resolution: "@grafana/scenes@npm:5.1.2"
dependencies: dependencies:
"@grafana/e2e-selectors": "npm:^11.0.0" "@grafana/e2e-selectors": "npm:^11.0.0"
"@leeoniya/ufuzzy": "npm:^1.0.14" "@leeoniya/ufuzzy": "npm:^1.0.14"
@ -3571,7 +3571,7 @@ __metadata:
"@grafana/ui": ^10.4.1 "@grafana/ui": ^10.4.1
react: ^18.0.0 react: ^18.0.0
react-dom: ^18.0.0 react-dom: ^18.0.0
checksum: 10/d99e88ba26f6df34fa595656be20cdaee8e9e59a8928e80691bea5cd9646ef6534c9929f38a5fcca1b09891a2609d4ee8e96f898799c5ec73b1993abe09b094a checksum: 10/814fe81537d267640cf0e4d91c1fc5805290fd6f46bbf37633edfbc8fbeae8a064a2b9540d482adb061235bd20148ddf246db6e41a8141380e98d49ca31638f3
languageName: node languageName: node
linkType: hard linkType: hard
@ -3684,6 +3684,7 @@ __metadata:
"@storybook/react": "npm:^8.1.6" "@storybook/react": "npm:^8.1.6"
"@storybook/react-webpack5": "npm:^8.1.6" "@storybook/react-webpack5": "npm:^8.1.6"
"@storybook/theming": "npm:^8.1.6" "@storybook/theming": "npm:^8.1.6"
"@tanstack/react-virtual": "npm:^3.5.1"
"@testing-library/dom": "npm:10.0.0" "@testing-library/dom": "npm:10.0.0"
"@testing-library/jest-dom": "npm:6.4.2" "@testing-library/jest-dom": "npm:6.4.2"
"@testing-library/react": "npm:15.0.2" "@testing-library/react": "npm:15.0.2"
@ -7803,6 +7804,25 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@tanstack/react-virtual@npm:^3.5.1":
version: 3.5.1
resolution: "@tanstack/react-virtual@npm:3.5.1"
dependencies:
"@tanstack/virtual-core": "npm:3.5.1"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: 10/11c8e9e2391fa0c947848a720b7dccccb1e35a78ac3169d1c34629bbec4ec713eed78d4c17a3e540e01386ee25b600a53254357597ae91a5fe35c7436651e975
languageName: node
linkType: hard
"@tanstack/virtual-core@npm:3.5.1":
version: 3.5.1
resolution: "@tanstack/virtual-core@npm:3.5.1"
checksum: 10/611ea09d37cf9183a51d2dfce401c3802b0d91f014e9bbaf32a6220ec7301b873b308130b795d935c0f5b73a43fd8358274915885da692d3e991eeeab6f8711b
languageName: node
linkType: hard
"@testing-library/dom@npm:10.0.0, @testing-library/dom@npm:>=7, @testing-library/dom@npm:^10.0.0": "@testing-library/dom@npm:10.0.0, @testing-library/dom@npm:>=7, @testing-library/dom@npm:^10.0.0":
version: 10.0.0 version: 10.0.0
resolution: "@testing-library/dom@npm:10.0.0" resolution: "@testing-library/dom@npm:10.0.0"
@ -14704,7 +14724,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"entities@npm:^4.2.0, entities@npm:^4.3.0, entities@npm:^4.4.0": "entities@npm:^4.2.0, entities@npm:^4.4.0":
version: 4.4.0 version: 4.4.0
resolution: "entities@npm:4.4.0" resolution: "entities@npm:4.4.0"
checksum: 10/b627cb900e901cc7817037b83bf993a1cbf6a64850540f7526af7bcf9c7d09ebc671198e6182cfae4680f733799e2852e6a1c46aa62ff36eb99680057a038df5 checksum: 10/b627cb900e901cc7817037b83bf993a1cbf6a64850540f7526af7bcf9c7d09ebc671198e6182cfae4680f733799e2852e6a1c46aa62ff36eb99680057a038df5
@ -17883,19 +17903,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"htmlparser2@npm:^8.0.1": "htmlparser2@npm:^8.0.1, htmlparser2@npm:^8.0.2":
version: 8.0.1
resolution: "htmlparser2@npm:8.0.1"
dependencies:
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.2"
domutils: "npm:^3.0.1"
entities: "npm:^4.3.0"
checksum: 10/f891041c331ef7ef300f1e8f0e6756d663cf8096f8a343a1bf474e7a5ce34fe7cd71b9dfb0227277f7de2007e847ef2a447e8b48eab592d6f3631aae18301d22
languageName: node
linkType: hard
"htmlparser2@npm:^8.0.2":
version: 8.0.2 version: 8.0.2
resolution: "htmlparser2@npm:8.0.2" resolution: "htmlparser2@npm:8.0.2"
dependencies: dependencies:
@ -24769,7 +24777,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"punycode@npm:2.3.1": "punycode@npm:2.3.1, punycode@npm:^2.1.0, punycode@npm:^2.1.1":
version: 2.3.1 version: 2.3.1
resolution: "punycode@npm:2.3.1" resolution: "punycode@npm:2.3.1"
checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059
@ -24783,13 +24791,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"punycode@npm:^2.1.0, punycode@npm:^2.1.1":
version: 2.1.1
resolution: "punycode@npm:2.1.1"
checksum: 10/939daa010c2cacebdb060c40ecb52fef0a739324a66f7fffe0f94353a1ee83e3b455e9032054c4a0c4977b0a28e27086f2171c392832b59a01bd948fd8e20914
languageName: node
linkType: hard
"pure-rand@npm:^6.0.0": "pure-rand@npm:^6.0.0":
version: 6.0.3 version: 6.0.3
resolution: "pure-rand@npm:6.0.3" resolution: "pure-rand@npm:6.0.3"

Loading…
Cancel
Save