mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 12:14:08 -06:00
merge main
This commit is contained in:
commit
a115bb6214
7
.github/workflows/trivy-scan.yml
vendored
7
.github/workflows/trivy-scan.yml
vendored
@ -1,9 +1,14 @@
|
||||
name: Trivy Scan
|
||||
on:
|
||||
pull_request:
|
||||
# only run on PRs where go.mod/go.sum/etc have been updated
|
||||
paths:
|
||||
- go.*
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- go.*
|
||||
|
||||
jobs:
|
||||
trivy-scan:
|
||||
@ -25,6 +30,8 @@ jobs:
|
||||
vuln-type: 'os,library'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
trivyignores: .trivyignore
|
||||
# for the PR check, ignore JS-related issues
|
||||
skip-files: 'yarn.lock,package.json'
|
||||
- name: Run Trivy vulnerability scanner (SARIF)
|
||||
uses: aquasecurity/trivy-action@0.22.0
|
||||
with:
|
||||
|
@ -72,6 +72,10 @@
|
||||
"pr": {
|
||||
"type": "number",
|
||||
"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 |
|
||||
| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) |
|
||||
| `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 |
|
||||
| `mlExpressions` | Enable support for Machine Learning in server-side expressions |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `autofixDSUID` | Automatically migrates invalid datasource UIDs |
|
||||
| `logsExploreTableDefaultVisualization` | Sets the logs table as default visualisation in logs explore |
|
||||
| `newDashboardSharingComponent` | Enables the new sharing drawer 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. |
|
||||
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars |
|
||||
| `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. |
|
||||
|
||||
## 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.
|
||||
|
||||
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" >}}
|
||||
|
||||
|
39
go.mod
39
go.mod
@ -33,7 +33,7 @@ require (
|
||||
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/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/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad
|
||||
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/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-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/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/modern-go/reflect2 v1.0.2 // @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/jpillora/backoff v1.0.0 // 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/compress v1.17.8 // 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/encoding v0.3.6 // 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/vfsgen v0.0.0-20200824052919-0d455de96546 // 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/proto/otlp v1.2.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/term v0.21.0 // 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/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/kms v0.29.2 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.4 // indirect
|
||||
modernc.org/mathutil v1.5.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/libc v1.41.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.29.6 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.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/yaml v1.4.0 // indirect; @grafana-app-platform-squad
|
||||
)
|
||||
|
||||
require github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240620135321-10b6011dd787
|
||||
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.5.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/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/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/mfridman/interpolate v0.0.2 // 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/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/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/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.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
|
||||
|
56
go.sum
56
go.sum
@ -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/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/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U=
|
||||
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/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4=
|
||||
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/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
|
||||
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 v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
||||
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-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
|
||||
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/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
|
||||
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.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/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=
|
||||
@ -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/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/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/go.mod h1:eNfbB9f2jM8o9RfwqwjY8SYm5tvowJ8Ly+iE4P9rXII=
|
||||
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 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/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/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=
|
||||
@ -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-20190608224051-11cab39313c9/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/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
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/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/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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
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.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.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
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.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
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/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
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.6.1-0.20240214161942-b65008136246/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
|
||||
github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs=
|
||||
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.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
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.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||
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/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=
|
||||
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/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 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.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-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
|
||||
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=
|
||||
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.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
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.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.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.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
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-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.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 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
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/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/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||
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.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
||||
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.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.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.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.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
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.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.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.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
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.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.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.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
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.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/token v1.0.0/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/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
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=
|
||||
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=
|
||||
|
@ -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/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
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/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/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo=
|
||||
github.com/jaegertracing/jaeger v1.41.0 h1:vVNky8dP46M2RjGaZ7qRENqylW+tBFay3h57N16Ip7M=
|
||||
|
@ -82,7 +82,6 @@ export interface FeatureToggles {
|
||||
sqlDatasourceDatabaseSelection?: boolean;
|
||||
recordedQueriesMulti?: boolean;
|
||||
vizAndWidgetSplit?: boolean;
|
||||
prometheusIncrementalQueryInstrumentation?: boolean;
|
||||
logsExploreTableVisualisation?: boolean;
|
||||
awsDatasourcesTempCredentials?: boolean;
|
||||
transformationsRedesign?: boolean;
|
||||
@ -180,7 +179,6 @@ export interface FeatureToggles {
|
||||
disableNumericMetricsSortingInExpressions?: boolean;
|
||||
grafanaManagedRecordingRules?: boolean;
|
||||
queryLibrary?: boolean;
|
||||
autofixDSUID?: boolean;
|
||||
logsExploreTableDefaultVisualization?: boolean;
|
||||
newDashboardSharingComponent?: boolean;
|
||||
alertingListViewV2?: boolean;
|
||||
@ -196,6 +194,7 @@ export interface FeatureToggles {
|
||||
authZGRPCServer?: boolean;
|
||||
openSearchBackendFlowEnabled?: boolean;
|
||||
ssoSettingsLDAP?: boolean;
|
||||
failWrongDSUID?: boolean;
|
||||
databaseReadReplica?: boolean;
|
||||
zanzana?: boolean;
|
||||
}
|
||||
|
@ -139,7 +139,6 @@ export class PrometheusDatasource
|
||||
this.cache = new QueryCache({
|
||||
getTargetSignature: this.getPrometheusTargetSignature.bind(this),
|
||||
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
|
||||
@ -162,14 +161,6 @@ export class PrometheusDatasource
|
||||
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
|
||||
* @param request
|
||||
|
@ -6,7 +6,7 @@ import { DataFrame, DataQueryRequest, DateTime, dateTime, TimeRange } from '@gra
|
||||
import { QueryEditorMode } from '../querybuilder/shared/types';
|
||||
import { PromQuery } from '../types';
|
||||
|
||||
import { DatasourceProfileData, QueryCache } from './QueryCache';
|
||||
import { QueryCache } from './QueryCache';
|
||||
import { IncrementalStorageDataFrameScenarios } from './QueryCacheTestData';
|
||||
|
||||
// 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 () {
|
||||
it('instantiates', () => {
|
||||
const storage = new QueryCache({
|
||||
@ -192,7 +184,6 @@ describe('QueryCache: Prometheus', function () {
|
||||
const storage = new QueryCache<PromQuery>({
|
||||
getTargetSignature: getPrometheusTargetSignature,
|
||||
overlapString: '10m',
|
||||
profileFunction: getPromProfileData,
|
||||
});
|
||||
const firstFrames = scenario.first.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>({
|
||||
getTargetSignature: getPrometheusTargetSignature,
|
||||
overlapString: '10m',
|
||||
profileFunction: getPromProfileData,
|
||||
});
|
||||
|
||||
// Initial request with all data for time range
|
||||
@ -489,7 +479,6 @@ describe('QueryCache: Prometheus', function () {
|
||||
const storage = new QueryCache<PromQuery>({
|
||||
getTargetSignature: getPrometheusTargetSignature,
|
||||
overlapString: '10m',
|
||||
profileFunction: getPromProfileData,
|
||||
});
|
||||
const cacheRequest = storage.requestInfo(request);
|
||||
expect(cacheRequest.requests[0]).toBe(request);
|
||||
@ -501,7 +490,6 @@ describe('QueryCache: Prometheus', function () {
|
||||
const storage = new QueryCache<PromQuery>({
|
||||
getTargetSignature: getPrometheusTargetSignature,
|
||||
overlapString: '10m',
|
||||
profileFunction: getPromProfileData,
|
||||
});
|
||||
const cacheRequest = storage.requestInfo(request);
|
||||
expect(cacheRequest.requests[0]).toBe(request);
|
||||
@ -513,7 +501,6 @@ describe('QueryCache: Prometheus', function () {
|
||||
const storage = new QueryCache<PromQuery>({
|
||||
getTargetSignature: getPrometheusTargetSignature,
|
||||
overlapString: '10m',
|
||||
profileFunction: getPromProfileData,
|
||||
});
|
||||
const cacheRequest = storage.requestInfo(request);
|
||||
expect(cacheRequest.requests[0]).toBe(request);
|
||||
|
@ -9,8 +9,6 @@ import {
|
||||
isValidDuration,
|
||||
parseDuration,
|
||||
} 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 { 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)
|
||||
type TargetIdent = string;
|
||||
|
||||
type RequestID = string;
|
||||
|
||||
// query + template variables + interval + raw time range
|
||||
// used for full target cache busting -> full range re-query
|
||||
type TargetSig = string;
|
||||
@ -44,22 +40,6 @@ export interface CacheRequestInfo<T extends SupportedQueryTypes> {
|
||||
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
|
||||
* 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> {
|
||||
private overlapWindowMs: number;
|
||||
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>();
|
||||
|
||||
constructor(options: {
|
||||
getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
|
||||
overlapString: string;
|
||||
profileFunction?: (request: DataQueryRequest<T>, target: T) => DatasourceProfileData;
|
||||
}) {
|
||||
const unverifiedOverlap = options.overlapString;
|
||||
if (isValidDuration(unverifiedOverlap)) {
|
||||
@ -120,132 +72,9 @@ export class QueryCache<T extends SupportedQueryTypes> {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
requestInfo(request: DataQueryRequest<T>): CacheRequestInfo<T> {
|
||||
// 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 prevTo: TimestampMs | undefined = undefined;
|
||||
|
||||
const refreshIntervalMs = request.intervalMs;
|
||||
|
||||
// pre-compute reqTargSigs
|
||||
const reqTargSigs = new Map<TargetIdent, TargetSig>();
|
||||
request.targets.forEach((targ) => {
|
||||
let targIdent = `${request.dashboardUID}|${request.panelId}|${targ.refId}`;
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -61,6 +61,7 @@
|
||||
"@react-aria/focus": "3.17.1",
|
||||
"@react-aria/overlays": "3.22.1",
|
||||
"@react-aria/utils": "3.24.1",
|
||||
"@tanstack/react-virtual": "^3.5.1",
|
||||
"ansicolor": "1.1.100",
|
||||
"calculate-size": "1.1.1",
|
||||
"classnames": "2.5.1",
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React, { useState } from 'react';
|
||||
import { Meta, StoryFn, StoryObj } from '@storybook/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',
|
||||
component: Combobox,
|
||||
args: {
|
||||
@ -28,9 +33,11 @@ const meta: Meta<typeof Combobox> = {
|
||||
],
|
||||
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);
|
||||
return (
|
||||
<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;
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
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 { Input, Props as InputProps } from '../Input/Input';
|
||||
|
||||
type Value = string | number;
|
||||
type Option = {
|
||||
export type Value = string | number;
|
||||
export type Option = {
|
||||
label: string;
|
||||
value: Value;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
interface ComboboxProps
|
||||
@ -33,32 +37,86 @@ function itemFilter(inputValue: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function estimateSize() {
|
||||
return 60;
|
||||
}
|
||||
|
||||
export const Combobox = ({ options, onChange, value, ...restProps }: ComboboxProps) => {
|
||||
const [items, setItems] = useState(options);
|
||||
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({
|
||||
items,
|
||||
itemToString,
|
||||
selectedItem,
|
||||
scrollIntoView: () => {},
|
||||
onInputValueChange: ({ inputValue }) => {
|
||||
setItems(options.filter(itemFilter(inputValue)));
|
||||
},
|
||||
onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem),
|
||||
onHighlightedIndexChange: ({ highlightedIndex, type }) => {
|
||||
if (type !== useCombobox.stateChangeTypes.MenuMouseLeave) {
|
||||
rowVirtualizer.scrollToIndex(highlightedIndex);
|
||||
}
|
||||
},
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<Input suffix={<Icon name={isOpen ? 'search' : 'angle-down'} />} {...restProps} {...getInputProps()} />
|
||||
<ul {...getMenuProps()}>
|
||||
{isOpen &&
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<li key={item.value} {...getItemProps({ item, index })}>
|
||||
{item.label}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div className={styles.dropdown} {...getMenuProps({ ref: listRef })}>
|
||||
{isOpen && (
|
||||
<ul style={{ height: rowVirtualizer.getTotalSize() }}>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
return (
|
||||
<li
|
||||
key={items[virtualRow.index].value}
|
||||
{...getItemProps({ item: items[virtualRow.index], index: virtualRow.index })}
|
||||
data-index={virtualRow.index}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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 styles = useStyles2(getStyles);
|
||||
const panelContentId = useId();
|
||||
const panelTitleId = useId().replace(/:/g, '_');
|
||||
|
||||
const hasHeader = !hoverHeader;
|
||||
|
||||
@ -179,7 +180,13 @@ export function PanelChrome({
|
||||
{/* Non collapsible title */}
|
||||
{!collapsible && 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}
|
||||
</Text>
|
||||
</div>
|
||||
@ -206,7 +213,7 @@ export function PanelChrome({
|
||||
aria-hidden={!!title}
|
||||
aria-label={!title ? 'toggle collapse panel' : undefined}
|
||||
/>
|
||||
<Text variant="h6" truncate>
|
||||
<Text variant="h6" truncate id={panelTitleId}>
|
||||
{title}
|
||||
</Text>
|
||||
</button>
|
||||
@ -249,6 +256,7 @@ export function PanelChrome({
|
||||
<section
|
||||
className={cx(styles.container, { [styles.transparentContainer]: isPanelTransparent })}
|
||||
style={containerStyles}
|
||||
aria-labelledby={!!title ? panelTitleId : undefined}
|
||||
data-testid={testid}
|
||||
tabIndex={0} // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
|
||||
onFocus={onFocus}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
@ -99,6 +100,8 @@ func SetupConfig(
|
||||
handler = filters.WithAcceptHeader(handler)
|
||||
handler = filters.WithPathRewriters(handler, pathRewriters)
|
||||
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
|
||||
// Configure filters.WithPanicRecovery to not crash on panic
|
||||
utilruntime.ReallyCrash = false
|
||||
|
||||
return handler
|
||||
}
|
||||
|
@ -21,25 +21,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type ZanzanaClient interface{}
|
||||
|
||||
// ProvideZanzana used to register ZanzanaClient.
|
||||
// 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) {
|
||||
return zanzana.NoopClient{}, nil
|
||||
}
|
||||
|
||||
logger := log.New("zanzana")
|
||||
|
||||
var client *zanzana.Client
|
||||
var client zanzana.Client
|
||||
switch cfg.Zanzana.Mode {
|
||||
case setting.ZanzanaModeClient:
|
||||
conn, err := grpc.NewClient(cfg.Zanzana.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
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:
|
||||
store, err := zanzana.NewEmbeddedStore(cfg, db, logger)
|
||||
if err != nil {
|
||||
@ -53,7 +51,7 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
|
||||
|
||||
channel := &inprocgrpc.Channel{}
|
||||
openfgav1.RegisterOpenFGAServiceServer(channel, srv)
|
||||
client = zanzana.NewClient(openfgav1.NewOpenFGAServiceClient(channel))
|
||||
client = zanzana.NewClient(channel)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.Zanzana.Mode)
|
||||
}
|
||||
|
@ -1,16 +1,46 @@
|
||||
package zanzana
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
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
|
||||
type Client struct {
|
||||
c openfgav1.OpenFGAServiceClient
|
||||
// Client is a wrapper around OpenFGAServiceClient with only methods using in Grafana included.
|
||||
type Client interface {
|
||||
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)
|
||||
}
|
||||
|
||||
func NewClient(c openfgav1.OpenFGAServiceClient) *Client {
|
||||
return &Client{c}
|
||||
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 (c *zanzanaClient) Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error) {
|
||||
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{}
|
||||
|
||||
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."))
|
||||
ErrDataSourceURLInvalid = errutil.ValidationFailed("datasource.urlInvalid", errutil.WithPublicMessage("Invalid datasource url."))
|
||||
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
|
||||
} else if err := util.ValidateUID(cmd.UID); err != nil {
|
||||
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "create", err)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagAutofixDSUID) {
|
||||
return fmt.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagFailWrongDSUID) {
|
||||
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 err := util.ValidateUID(cmd.UID); err != nil {
|
||||
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "update", err)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagAutofixDSUID) {
|
||||
cmd.UID = util.AutofixUID(cmd.UID)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagFailWrongDSUID) {
|
||||
return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ func TestIntegrationDataAccess(t *testing.T) {
|
||||
ss := SqlStore{
|
||||
db: db,
|
||||
logger: log.NewNopLogger(),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAutofixDSUID),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagFailWrongDSUID),
|
||||
}
|
||||
cmd := defaultAddDatasourceCommand
|
||||
cmd.UID = "test/uid"
|
||||
@ -232,28 +232,21 @@ func TestIntegrationDataAccess(t *testing.T) {
|
||||
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)
|
||||
ds := initDatasource(db)
|
||||
ss := SqlStore{
|
||||
db: db,
|
||||
logger: log.NewNopLogger(),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAutofixDSUID),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagFailWrongDSUID),
|
||||
}
|
||||
require.NotEmpty(t, ds.UID)
|
||||
|
||||
cmd := defaultUpdateDatasourceCommand
|
||||
cmd.ID = ds.ID
|
||||
cmd.UID = "new/uid"
|
||||
res, err := ss.UpdateDataSource(context.Background(), &cmd)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
_, err := ss.UpdateDataSource(context.Background(), &cmd)
|
||||
require.ErrorContains(t, err, "invalid format of UID")
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -480,13 +480,6 @@ var (
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDashboardsSquad,
|
||||
},
|
||||
{
|
||||
Name: "prometheusIncrementalQueryInstrumentation",
|
||||
Description: "Adds RudderStack events to incremental queries",
|
||||
FrontendOnly: true,
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaObservabilityMetricsSquad,
|
||||
},
|
||||
{
|
||||
Name: "logsExploreTableVisualisation",
|
||||
Description: "A table visualisation for logs in Explore",
|
||||
@ -1222,12 +1215,6 @@ var (
|
||||
FrontendOnly: false,
|
||||
AllowSelfServe: false,
|
||||
},
|
||||
{
|
||||
Name: "autofixDSUID",
|
||||
Description: "Automatically migrates invalid datasource UIDs",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "logsExploreTableDefaultVisualization",
|
||||
Description: "Sets the logs table as default visualisation in logs explore",
|
||||
@ -1336,6 +1323,12 @@ var (
|
||||
HideFromDocs: true,
|
||||
HideFromAdminPage: true,
|
||||
},
|
||||
{
|
||||
Name: "failWrongDSUID",
|
||||
Description: "Throws an error if a datasource has an invalid UIDs",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "databaseReadReplica",
|
||||
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
|
||||
recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false
|
||||
vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,true
|
||||
prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,true
|
||||
logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true
|
||||
awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false
|
||||
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
|
||||
grafanaManagedRecordingRules,experimental,@grafana/alerting-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
|
||||
newDashboardSharingComponent,experimental,@grafana/sharing-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
|
||||
openSearchBackendFlowEnabled,preview,@grafana/aws-datasources,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
|
||||
zanzana,experimental,@grafana/identity-access-team,false,false,false
|
||||
|
|
@ -263,10 +263,6 @@ const (
|
||||
// Split panels between visualizations and widgets
|
||||
FlagVizAndWidgetSplit = "vizAndWidgetSplit"
|
||||
|
||||
// FlagPrometheusIncrementalQueryInstrumentation
|
||||
// Adds RudderStack events to incremental queries
|
||||
FlagPrometheusIncrementalQueryInstrumentation = "prometheusIncrementalQueryInstrumentation"
|
||||
|
||||
// FlagLogsExploreTableVisualisation
|
||||
// A table visualisation for logs in Explore
|
||||
FlagLogsExploreTableVisualisation = "logsExploreTableVisualisation"
|
||||
@ -655,10 +651,6 @@ const (
|
||||
// Enables Query Library feature in Explore
|
||||
FlagQueryLibrary = "queryLibrary"
|
||||
|
||||
// FlagAutofixDSUID
|
||||
// Automatically migrates invalid datasource UIDs
|
||||
FlagAutofixDSUID = "autofixDSUID"
|
||||
|
||||
// FlagLogsExploreTableDefaultVisualization
|
||||
// Sets the logs table as default visualisation in logs explore
|
||||
FlagLogsExploreTableDefaultVisualization = "logsExploreTableDefaultVisualization"
|
||||
@ -719,6 +711,10 @@ const (
|
||||
// Use the new SSO Settings API to configure LDAP
|
||||
FlagSsoSettingsLDAP = "ssoSettingsLDAP"
|
||||
|
||||
// FlagFailWrongDSUID
|
||||
// Throws an error if a datasource has an invalid UIDs
|
||||
FlagFailWrongDSUID = "failWrongDSUID"
|
||||
|
||||
// FlagDatabaseReadReplica
|
||||
// Use a read replica for some database queries.
|
||||
FlagDatabaseReadReplica = "databaseReadReplica"
|
||||
|
@ -401,8 +401,9 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "autofixDSUID",
|
||||
"resourceVersion": "1718727528075",
|
||||
"creationTimestamp": "2024-05-03T11:32:07Z"
|
||||
"resourceVersion": "1717578796182",
|
||||
"creationTimestamp": "2024-05-03T11:32:07Z",
|
||||
"deletionTimestamp": "2024-06-18T14:28:32Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Automatically migrates invalid datasource UIDs",
|
||||
@ -915,6 +916,18 @@
|
||||
"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": {
|
||||
"name": "faroDatasourceSelector",
|
||||
@ -1819,7 +1832,8 @@
|
||||
"metadata": {
|
||||
"name": "prometheusIncrementalQueryInstrumentation",
|
||||
"resourceVersion": "1718727528075",
|
||||
"creationTimestamp": "2023-07-05T19:39:49Z"
|
||||
"creationTimestamp": "2023-07-05T19:39:49Z",
|
||||
"deletionTimestamp": "2024-06-20T11:30:37Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Adds RudderStack events to incremental queries",
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
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/store"
|
||||
)
|
||||
@ -22,8 +21,8 @@ type FakeRuleService struct {
|
||||
AuthorizeDatasourceAccessForRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error
|
||||
HasAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) (bool, error)
|
||||
AuthorizeAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error
|
||||
HasAccessInFolderFunc func(context.Context, identity.Requester, accesscontrol.Namespaced) (bool, error)
|
||||
AuthorizeAccessInFolderFunc func(context.Context, identity.Requester, accesscontrol.Namespaced) error
|
||||
HasAccessInFolderFunc func(context.Context, identity.Requester, models.Namespaced) (bool, error)
|
||||
AuthorizeAccessInFolderFunc func(context.Context, identity.Requester, models.Namespaced) error
|
||||
AuthorizeRuleChangesFunc func(context.Context, identity.Requester, *store.GroupDelta) error
|
||||
|
||||
Calls []Call
|
||||
@ -77,7 +76,7 @@ func (s *FakeRuleService) AuthorizeAccessToRuleGroup(ctx context.Context, user i
|
||||
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}})
|
||||
if s.HasAccessInFolderFunc != nil {
|
||||
return s.HasAccessInFolderFunc(ctx, user, namespaced)
|
||||
@ -85,7 +84,7 @@ func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.R
|
||||
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}})
|
||||
if s.AuthorizeAccessInFolderFunc != nil {
|
||||
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
|
||||
func getReadFolderAccessEvaluator(folderUID string) accesscontrol.Evaluator {
|
||||
return accesscontrol.EvalAll(
|
||||
@ -130,7 +126,7 @@ func (r *RuleService) AuthorizeAccessToRuleGroup(ctx context.Context, user ident
|
||||
// - ("folders:read") read 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.
|
||||
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()))
|
||||
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
|
||||
// - ("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
|
||||
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()))
|
||||
return r.HasAccessOrError(ctx, user, eval, func() string {
|
||||
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
|
||||
AuthorizeDatasourceAccessForRule(ctx context.Context, user identity.Requester, rule *models.AlertRule) 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.
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
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/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
@ -147,7 +146,7 @@ func (f fakeRuleAccessControlService) AuthorizeAccessToRuleGroup(ctx context.Con
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
@ -24,7 +23,7 @@ type SilenceService struct {
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
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/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -61,7 +60,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
user := ac.BackgroundUser("test", 1, org.RoleNone, nil)
|
||||
t.Run("Attach rule metadata to silences", func(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -134,7 +133,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
})
|
||||
t.Run("Don't check same namespace access more than once", func(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -159,7 +158,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
require.NoError(t, svc.WithRuleMetadata(context.Background(), user, silencesWithMetadata...))
|
||||
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, "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"
|
||||
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/store"
|
||||
)
|
||||
@ -13,7 +12,7 @@ import (
|
||||
type RuleAccessControlService interface {
|
||||
HasAccess(ctx context.Context, user identity.Requester, evaluator ac.Evaluator) (bool, 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
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"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/models"
|
||||
"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) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -231,7 +230,7 @@ func TestAuthorizeAccessToRule(t *testing.T) {
|
||||
return false, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1020,7 @@ func TestGetAlertRule(t *testing.T) {
|
||||
service, _, _, ac := initServiceWithData(t)
|
||||
|
||||
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.EqualValues(t, rule, namespaced)
|
||||
return expected
|
||||
@ -1034,7 +1034,7 @@ func TestGetAlertRule(t *testing.T) {
|
||||
assert.Equal(t, "AuthorizeRuleRead", ac.Calls[0].Method)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
"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/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@ -161,7 +160,7 @@ type fakeRuleAccessControlService struct {
|
||||
mu sync.Mutex
|
||||
Calls []call
|
||||
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
|
||||
CanReadAllRulesFunc 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
|
||||
|
||||
cache Cache[T]
|
||||
cache channelCache[T]
|
||||
subscribe chan chan T
|
||||
unsubscribe chan (<-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
|
||||
b.shouldTerminate = ctx.Done()
|
||||
b.cache = NewCache[T](ctx, 100)
|
||||
b.cache = newChannelCache[T](ctx, 100)
|
||||
b.subscribe = make(chan chan T, 100)
|
||||
b.unsubscribe = make(chan (<-chan T), 100)
|
||||
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
|
||||
Add(item T)
|
||||
Get(i int) T
|
||||
@ -260,12 +260,12 @@ type cache[T any] struct {
|
||||
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.ctx = ctx
|
||||
if size <= 0 {
|
||||
size = DefaultCacheSize
|
||||
size = defaultCacheSize
|
||||
}
|
||||
c.size = size
|
||||
c.cache = make([]T, c.size)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
c := NewCache[int](context.Background(), 10)
|
||||
c := newChannelCache[int](context.Background(), 10)
|
||||
|
||||
e := []int{}
|
||||
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
|
||||
var validUIDCharPattern = `a-zA-Z0-9\-\_`
|
||||
var validUIDPattern = regexp.MustCompile(`^[` + validUIDCharPattern + `]*$`).MatchString
|
||||
var validUIDReplacer = regexp.MustCompile(`[^` + validUIDCharPattern + `]`).ReplaceAllString
|
||||
|
||||
// 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
|
||||
@ -93,13 +92,3 @@ func ValidateUID(uid string) error {
|
||||
}
|
||||
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 { fetchNodes, fetchScope, fetchSelectedScopes } from './api';
|
||||
import { NodesMap, SelectedScope, TreeScope } from './types';
|
||||
import { getBasicScope } from './utils';
|
||||
|
||||
export interface ScopesFiltersSceneState extends SceneObjectState {
|
||||
nodes: NodesMap;
|
||||
@ -180,9 +181,16 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
||||
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,
|
||||
});
|
||||
|
||||
this.setState({ scopes: await fetchSelectedScopes(treeScopes), isLoadingScopes: false });
|
||||
const scopes = await fetchSelectedScopes(treeScopes);
|
||||
|
||||
this.setState({ scopes, isLoadingScopes: false });
|
||||
}
|
||||
|
||||
public resetDirtyScopeNames() {
|
||||
|
@ -30,7 +30,7 @@ export class ScopesScene extends SceneObjectBase<ScopesSceneState> {
|
||||
this.addActivationHandler(() => {
|
||||
this._subs.add(
|
||||
this.state.filters.subscribeToState((newState, prevState) => {
|
||||
if (newState.scopes !== prevState.scopes) {
|
||||
if (!newState.isLoadingScopes && newState.scopes !== prevState.scopes) {
|
||||
if (this.state.isExpanded) {
|
||||
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 { NodesMap, SelectedScope, SuggestedDashboard, TreeScope } from './types';
|
||||
import { getBasicScope, mergeScopes } from './utils';
|
||||
|
||||
const group = 'scope.grafana.app';
|
||||
const version = 'v0alpha1';
|
||||
@ -48,31 +49,12 @@ export async function fetchScope(name: string): Promise<Scope> {
|
||||
}
|
||||
|
||||
const response = new Promise<Scope>(async (resolve) => {
|
||||
const basicScope: Scope = {
|
||||
metadata: { name },
|
||||
spec: {
|
||||
filters: [],
|
||||
title: name,
|
||||
type: '',
|
||||
category: '',
|
||||
description: '',
|
||||
},
|
||||
};
|
||||
const basicScope = getBasicScope(name);
|
||||
|
||||
try {
|
||||
const serverScope = await scopesClient.get(name);
|
||||
|
||||
const scope = {
|
||||
...basicScope,
|
||||
metadata: {
|
||||
...basicScope.metadata,
|
||||
...serverScope.metadata,
|
||||
},
|
||||
spec: {
|
||||
...basicScope.spec,
|
||||
...serverScope.spec,
|
||||
},
|
||||
};
|
||||
const scope = mergeScopes(basicScope, serverScope);
|
||||
|
||||
resolve(scope);
|
||||
} catch (err) {
|
||||
|
28
public/app/features/dashboard-scene/scene/Scopes/utils.ts
Normal file
28
public/app/features/dashboard-scene/scene/Scopes/utils.ts
Normal file
@ -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[];
|
||||
register: RegisterFunction;
|
||||
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;
|
||||
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) => {
|
||||
setOutlineItems((prevItems) =>
|
||||
prevItems.map((item) => {
|
||||
if (item.id === parentId) {
|
||||
item.children = item.children?.filter((child) => child.type !== childType);
|
||||
const unregisterAllChildren = useCallback(
|
||||
(parentIdGetter: (items: ContentOutlineItemContextProps[]) => string | undefined, childType: ITEM_TYPES) => {
|
||||
setOutlineItems((prevItems) => {
|
||||
const parentId = parentIdGetter(prevItems);
|
||||
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(() => {
|
||||
setOutlineItems((prevItems) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 {
|
||||
@ -154,8 +154,10 @@ export function ExploreGraph({
|
||||
|
||||
const structureRev = useStructureRev(dataWithConfig);
|
||||
|
||||
const onHiddenSeriesChangedRef = useRef(onHiddenSeriesChanged);
|
||||
|
||||
useEffect(() => {
|
||||
if (onHiddenSeriesChanged) {
|
||||
if (onHiddenSeriesChangedRef.current) {
|
||||
const hiddenFrames: string[] = [];
|
||||
dataWithConfig.forEach((frame) => {
|
||||
const allFieldsHidden = frame.fields.map((field) => field.config?.custom?.hideFrom?.viz).every(identity);
|
||||
@ -163,9 +165,9 @@ export function ExploreGraph({
|
||||
hiddenFrames.push(getFrameDisplayName(frame));
|
||||
}
|
||||
});
|
||||
onHiddenSeriesChanged(hiddenFrames);
|
||||
onHiddenSeriesChangedRef.current(hiddenFrames);
|
||||
}
|
||||
}, [dataWithConfig, onHiddenSeriesChanged]);
|
||||
}, [dataWithConfig]);
|
||||
|
||||
const panelContext: PanelContext = {
|
||||
eventsScope: 'explore',
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
EventBusSrv,
|
||||
ExploreLogsPanelState,
|
||||
ExplorePanelsState,
|
||||
LoadingState,
|
||||
LogLevel,
|
||||
@ -13,25 +13,20 @@ import {
|
||||
standardTransformersRegistry,
|
||||
toUtc,
|
||||
createDataFrame,
|
||||
ExploreLogsPanelState,
|
||||
} from '@grafana/data';
|
||||
import { organizeFieldsTransformer } from '@grafana/data/src/transformations/transformers/organize';
|
||||
import { config } from '@grafana/runtime';
|
||||
import store from 'app/core/store';
|
||||
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 { visualisationTypeKey } from './utils/logs';
|
||||
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();
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
@ -45,26 +40,11 @@ jest.mock('app/core/utils/shortLinks', () => ({
|
||||
createAndCopyShortLink: (url: string) => createAndCopyShortLink(url),
|
||||
}));
|
||||
|
||||
jest.mock('app/store/store', () => ({
|
||||
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();
|
||||
const fakeChangePanelState = jest.fn().mockReturnValue({ type: 'fakeAction' });
|
||||
jest.mock('../state/explorePane', () => ({
|
||||
...jest.requireActual('../state/explorePane'),
|
||||
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;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
@ -120,6 +101,7 @@ describe('Logs', () => {
|
||||
];
|
||||
|
||||
const testDataFrame = dataFrame ?? getMockLokiFrame();
|
||||
|
||||
return (
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
@ -157,8 +139,23 @@ describe('Logs', () => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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', () => {
|
||||
@ -216,40 +213,47 @@ describe('Logs', () => {
|
||||
|
||||
it('should render a load more button', () => {
|
||||
const scanningStarted = jest.fn();
|
||||
const store = configureStore({
|
||||
explore: {
|
||||
...initialExploreState,
|
||||
},
|
||||
});
|
||||
render(
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
splitOpen={() => undefined}
|
||||
logsVolumeEnabled={true}
|
||||
onSetLogsVolumeEnabled={() => null}
|
||||
onClickFilterLabel={() => null}
|
||||
onClickFilterOutLabel={() => null}
|
||||
logsVolumeData={undefined}
|
||||
loadLogsVolumeData={() => undefined}
|
||||
logRows={[]}
|
||||
onStartScanning={scanningStarted}
|
||||
timeZone={'utc'}
|
||||
width={50}
|
||||
loading={false}
|
||||
loadingState={LoadingState.Done}
|
||||
absoluteRange={{
|
||||
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'),
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
addResultsToCache={() => {}}
|
||||
onChangeTime={() => {}}
|
||||
clearCache={() => {}}
|
||||
getFieldLinks={() => {
|
||||
return [];
|
||||
}}
|
||||
eventBus={new EventBusSrv()}
|
||||
isFilterLabelActive={jest.fn()}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
splitOpen={() => undefined}
|
||||
logsVolumeEnabled={true}
|
||||
onSetLogsVolumeEnabled={() => null}
|
||||
onClickFilterLabel={() => null}
|
||||
onClickFilterOutLabel={() => null}
|
||||
logsVolumeData={undefined}
|
||||
loadLogsVolumeData={() => undefined}
|
||||
logRows={[]}
|
||||
onStartScanning={scanningStarted}
|
||||
timeZone={'utc'}
|
||||
width={50}
|
||||
loading={false}
|
||||
loadingState={LoadingState.Done}
|
||||
absoluteRange={{
|
||||
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'),
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
addResultsToCache={() => {}}
|
||||
onChangeTime={() => {}}
|
||||
clearCache={() => {}}
|
||||
getFieldLinks={() => {
|
||||
return [];
|
||||
}}
|
||||
eventBus={new EventBusSrv()}
|
||||
isFilterLabelActive={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
const button = screen.getByRole('button', {
|
||||
name: /scan for older logs/i,
|
||||
@ -259,40 +263,47 @@ describe('Logs', () => {
|
||||
});
|
||||
|
||||
it('should render a stop scanning button', () => {
|
||||
const store = configureStore({
|
||||
explore: {
|
||||
...initialExploreState,
|
||||
},
|
||||
});
|
||||
render(
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
splitOpen={() => undefined}
|
||||
logsVolumeEnabled={true}
|
||||
onSetLogsVolumeEnabled={() => null}
|
||||
onClickFilterLabel={() => null}
|
||||
onClickFilterOutLabel={() => null}
|
||||
logsVolumeData={undefined}
|
||||
loadLogsVolumeData={() => undefined}
|
||||
logRows={[]}
|
||||
scanning={true}
|
||||
timeZone={'utc'}
|
||||
width={50}
|
||||
loading={false}
|
||||
loadingState={LoadingState.Done}
|
||||
absoluteRange={{
|
||||
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'),
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
addResultsToCache={() => {}}
|
||||
onChangeTime={() => {}}
|
||||
clearCache={() => {}}
|
||||
getFieldLinks={() => {
|
||||
return [];
|
||||
}}
|
||||
eventBus={new EventBusSrv()}
|
||||
isFilterLabelActive={jest.fn()}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
splitOpen={() => undefined}
|
||||
logsVolumeEnabled={true}
|
||||
onSetLogsVolumeEnabled={() => null}
|
||||
onClickFilterLabel={() => null}
|
||||
onClickFilterOutLabel={() => null}
|
||||
logsVolumeData={undefined}
|
||||
loadLogsVolumeData={() => undefined}
|
||||
logRows={[]}
|
||||
scanning={true}
|
||||
timeZone={'utc'}
|
||||
width={50}
|
||||
loading={false}
|
||||
loadingState={LoadingState.Done}
|
||||
absoluteRange={{
|
||||
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'),
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
addResultsToCache={() => {}}
|
||||
onChangeTime={() => {}}
|
||||
clearCache={() => {}}
|
||||
getFieldLinks={() => {
|
||||
return [];
|
||||
}}
|
||||
eventBus={new EventBusSrv()}
|
||||
isFilterLabelActive={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
expect(
|
||||
@ -304,42 +315,48 @@ describe('Logs', () => {
|
||||
|
||||
it('should render a stop scanning button', () => {
|
||||
const scanningStopped = jest.fn();
|
||||
|
||||
const store = configureStore({
|
||||
explore: {
|
||||
...initialExploreState,
|
||||
},
|
||||
});
|
||||
render(
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
splitOpen={() => undefined}
|
||||
logsVolumeEnabled={true}
|
||||
onSetLogsVolumeEnabled={() => null}
|
||||
onClickFilterLabel={() => null}
|
||||
onClickFilterOutLabel={() => null}
|
||||
logsVolumeData={undefined}
|
||||
loadLogsVolumeData={() => undefined}
|
||||
logRows={[]}
|
||||
scanning={true}
|
||||
onStopScanning={scanningStopped}
|
||||
timeZone={'utc'}
|
||||
width={50}
|
||||
loading={false}
|
||||
loadingState={LoadingState.Done}
|
||||
absoluteRange={{
|
||||
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'),
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
addResultsToCache={() => {}}
|
||||
onChangeTime={() => {}}
|
||||
clearCache={() => {}}
|
||||
getFieldLinks={() => {
|
||||
return [];
|
||||
}}
|
||||
eventBus={new EventBusSrv()}
|
||||
isFilterLabelActive={jest.fn()}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<Logs
|
||||
exploreId={'left'}
|
||||
splitOpen={() => undefined}
|
||||
logsVolumeEnabled={true}
|
||||
onSetLogsVolumeEnabled={() => null}
|
||||
onClickFilterLabel={() => null}
|
||||
onClickFilterOutLabel={() => null}
|
||||
logsVolumeData={undefined}
|
||||
loadLogsVolumeData={() => undefined}
|
||||
logRows={[]}
|
||||
scanning={true}
|
||||
onStopScanning={scanningStopped}
|
||||
timeZone={'utc'}
|
||||
width={50}
|
||||
loading={false}
|
||||
loadingState={LoadingState.Done}
|
||||
absoluteRange={{
|
||||
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'),
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
addResultsToCache={() => {}}
|
||||
onChangeTime={() => {}}
|
||||
clearCache={() => {}}
|
||||
getFieldLinks={() => {
|
||||
return [];
|
||||
}}
|
||||
eventBus={new EventBusSrv()}
|
||||
isFilterLabelActive={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', {
|
||||
@ -363,12 +380,12 @@ describe('Logs', () => {
|
||||
describe('for permalinking', () => {
|
||||
it('should dispatch a `changePanelState` event without the id', () => {
|
||||
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(getComponent({ loading: false, exploreId: 'right', panelState }));
|
||||
rerender(<Provider store={store}>{getComponent({ loading: true, exploreId: 'right', panelState })}</Provider>);
|
||||
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', () => {
|
||||
@ -491,23 +508,17 @@ describe('Logs', () => {
|
||||
});
|
||||
|
||||
it('should use default state from localstorage - table', async () => {
|
||||
const oldGet = store.get;
|
||||
store.get = jest.fn().mockReturnValue('table');
|
||||
localStorage.setItem(visualisationTypeKey, 'table');
|
||||
setup({});
|
||||
const table = await screen.findByTestId('logRowsTable');
|
||||
expect(table).toBeInTheDocument();
|
||||
store.get = oldGet;
|
||||
});
|
||||
|
||||
it('should use default state from localstorage - logs', async () => {
|
||||
const oldGet = store.get;
|
||||
store.get = jest.fn().mockReturnValue('logs');
|
||||
localStorage.setItem(visualisationTypeKey, 'logs');
|
||||
setup({});
|
||||
const table = await screen.findByTestId('logRows');
|
||||
expect(table).toBeInTheDocument();
|
||||
store.get = oldGet;
|
||||
});
|
||||
|
||||
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 before = initialPanes.slice(0, action.meta.arg.position);
|
||||
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 }));
|
||||
|
||||
return (
|
||||
<InlineSearchField
|
||||
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."
|
||||
>
|
||||
<InlineSearchField label="Aggregate by" tooltip="Select one or more tags to see the metrics summary.">
|
||||
<>
|
||||
{query.groupBy?.map((f, i) => {
|
||||
const tags = getTags(f)
|
||||
|
@ -536,9 +536,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
|
||||
if (!response.data.summaries) {
|
||||
return {
|
||||
error: {
|
||||
message: getErrorMessage(
|
||||
`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 }`
|
||||
),
|
||||
message: getErrorMessage(`No summary data for '${groupBy}'.`),
|
||||
},
|
||||
data: emptyResponse,
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ describe('MetricsSummary', () => {
|
||||
"datasourceName": "tempo",
|
||||
"datasourceUid": "gdev-tempo",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
@ -83,7 +83,7 @@ describe('MetricsSummary', () => {
|
||||
"datasourceName": "tempo",
|
||||
"datasourceUid": "gdev-tempo",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
@ -99,19 +99,6 @@ describe('MetricsSummary', () => {
|
||||
38.1,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"width": 150,
|
||||
},
|
||||
"displayNameFromDS": "Kind",
|
||||
},
|
||||
"name": "kind",
|
||||
"type": "string",
|
||||
"values": [
|
||||
"server",
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
@ -222,7 +209,6 @@ describe('MetricsSummary', () => {
|
||||
{
|
||||
"contains_sink": "true",
|
||||
"errorPercentage": 10,
|
||||
"kind": "server",
|
||||
"p50": 1,
|
||||
"p90": 2,
|
||||
"p95": 3,
|
||||
@ -241,21 +227,21 @@ describe('MetricsSummary', () => {
|
||||
it('getConfigQuery should return correctly for empty target query', () => {
|
||||
const result = getConfigQuery(series, '{}');
|
||||
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', () => {
|
||||
const result = getConfigQuery(series, '{name="HTTP POST - post"} | by(resource.service.name)');
|
||||
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', () => {
|
||||
const result = getConfigQuery(series, 'by(resource.service.name)');
|
||||
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',
|
||||
fields: [
|
||||
...Object.values(dynamicMetrics).sort((a, b) => a.name.localeCompare(b.name)),
|
||||
{
|
||||
name: 'kind',
|
||||
type: FieldType.string,
|
||||
config: { displayNameFromDS: 'Kind', custom: { width: 150 } },
|
||||
},
|
||||
{
|
||||
name: 'spanCount',
|
||||
type: FieldType.number,
|
||||
@ -113,7 +108,6 @@ export const transformToMetricsData = (data: MetricsSummary) => {
|
||||
: '0%';
|
||||
|
||||
const metricsData: MetricsData = {
|
||||
kind: 'server', // so the user knows all results are of kind = server
|
||||
spanCount: getNumberForMetric(data.spanCount),
|
||||
errorPercentage,
|
||||
p50: getNumberForMetric(data.p50),
|
||||
@ -145,12 +139,12 @@ export const getConfigQuery = (series: Series[], targetQuery: string) => {
|
||||
configQuery = targetQuery.substring(0, closingBracketIndex);
|
||||
if (queryParts.length > 0) {
|
||||
configQuery += targetQuery.replace(/\s/g, '').includes('{}') ? '' : ' && ';
|
||||
configQuery += `${queryParts.join(' && ')} && kind=server`;
|
||||
configQuery += `${queryParts.join(' && ')}`;
|
||||
configQuery += `}`;
|
||||
}
|
||||
configQuery += `${queryAfterClosingBracket}`;
|
||||
} else {
|
||||
configQuery = `{${queryParts.join(' && ')} && kind=server} | ${targetQuery}`;
|
||||
configQuery = `{${queryParts.join(' && ')}} | ${targetQuery}`;
|
||||
}
|
||||
|
||||
return configQuery;
|
||||
|
@ -25,6 +25,14 @@
|
||||
"user": "Nutzer"
|
||||
}
|
||||
},
|
||||
"alert-labels": {
|
||||
"button": {
|
||||
"hide": "",
|
||||
"show": {
|
||||
"tooltip": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"alert-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@ -138,6 +146,16 @@
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"central-alert-history": {
|
||||
"error": "",
|
||||
"filter": {
|
||||
"button": {
|
||||
"clear": ""
|
||||
},
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
"inline-toast": {
|
||||
"success": "Kopiert"
|
||||
@ -1166,7 +1184,7 @@
|
||||
"public": {
|
||||
"title": "Öffentliche Dashboards"
|
||||
},
|
||||
"recentlyDeleted": {
|
||||
"recently-deleted": {
|
||||
"subtitle": "",
|
||||
"title": ""
|
||||
},
|
||||
@ -1615,6 +1633,7 @@
|
||||
"tree": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"headline": "",
|
||||
"search": ""
|
||||
}
|
||||
},
|
||||
|
@ -25,6 +25,14 @@
|
||||
"user": "Usuario"
|
||||
}
|
||||
},
|
||||
"alert-labels": {
|
||||
"button": {
|
||||
"hide": "",
|
||||
"show": {
|
||||
"tooltip": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"alert-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@ -138,6 +146,16 @@
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"central-alert-history": {
|
||||
"error": "",
|
||||
"filter": {
|
||||
"button": {
|
||||
"clear": ""
|
||||
},
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
"inline-toast": {
|
||||
"success": "Copiado"
|
||||
@ -1166,7 +1184,7 @@
|
||||
"public": {
|
||||
"title": "Paneles de control públicos"
|
||||
},
|
||||
"recentlyDeleted": {
|
||||
"recently-deleted": {
|
||||
"subtitle": "",
|
||||
"title": ""
|
||||
},
|
||||
@ -1615,6 +1633,7 @@
|
||||
"tree": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"headline": "",
|
||||
"search": ""
|
||||
}
|
||||
},
|
||||
|
@ -25,6 +25,14 @@
|
||||
"user": "Utilisateur"
|
||||
}
|
||||
},
|
||||
"alert-labels": {
|
||||
"button": {
|
||||
"hide": "",
|
||||
"show": {
|
||||
"tooltip": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"alert-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@ -138,6 +146,16 @@
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"central-alert-history": {
|
||||
"error": "",
|
||||
"filter": {
|
||||
"button": {
|
||||
"clear": ""
|
||||
},
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
"inline-toast": {
|
||||
"success": "Copié"
|
||||
@ -1166,7 +1184,7 @@
|
||||
"public": {
|
||||
"title": "Tableaux de bord publics"
|
||||
},
|
||||
"recentlyDeleted": {
|
||||
"recently-deleted": {
|
||||
"subtitle": "",
|
||||
"title": ""
|
||||
},
|
||||
@ -1615,6 +1633,7 @@
|
||||
"tree": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"headline": "",
|
||||
"search": ""
|
||||
}
|
||||
},
|
||||
|
@ -25,6 +25,14 @@
|
||||
"user": "Usuário"
|
||||
}
|
||||
},
|
||||
"alert-labels": {
|
||||
"button": {
|
||||
"hide": "",
|
||||
"show": {
|
||||
"tooltip": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"alert-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@ -138,6 +146,16 @@
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"central-alert-history": {
|
||||
"error": "",
|
||||
"filter": {
|
||||
"button": {
|
||||
"clear": ""
|
||||
},
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
"inline-toast": {
|
||||
"success": "Copiado"
|
||||
@ -1166,7 +1184,7 @@
|
||||
"public": {
|
||||
"title": "Painéis de controle públicos"
|
||||
},
|
||||
"recentlyDeleted": {
|
||||
"recently-deleted": {
|
||||
"subtitle": "",
|
||||
"title": ""
|
||||
},
|
||||
@ -1615,6 +1633,7 @@
|
||||
"tree": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"headline": "",
|
||||
"search": ""
|
||||
}
|
||||
},
|
||||
|
@ -25,6 +25,14 @@
|
||||
"user": "用户"
|
||||
}
|
||||
},
|
||||
"alert-labels": {
|
||||
"button": {
|
||||
"hide": "",
|
||||
"show": {
|
||||
"tooltip": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"alert-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@ -133,6 +141,16 @@
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"central-alert-history": {
|
||||
"error": "",
|
||||
"filter": {
|
||||
"button": {
|
||||
"clear": ""
|
||||
},
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
"inline-toast": {
|
||||
"success": "已复制"
|
||||
@ -1160,7 +1178,7 @@
|
||||
"public": {
|
||||
"title": "公共仪表板"
|
||||
},
|
||||
"recentlyDeleted": {
|
||||
"recently-deleted": {
|
||||
"subtitle": "",
|
||||
"title": ""
|
||||
},
|
||||
@ -1608,6 +1626,7 @@
|
||||
"tree": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"headline": "",
|
||||
"search": ""
|
||||
}
|
||||
},
|
||||
|
51
yarn.lock
51
yarn.lock
@ -3555,8 +3555,8 @@ __metadata:
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes@npm:^5.0.2":
|
||||
version: 5.0.3
|
||||
resolution: "@grafana/scenes@npm:5.0.3"
|
||||
version: 5.1.2
|
||||
resolution: "@grafana/scenes@npm:5.1.2"
|
||||
dependencies:
|
||||
"@grafana/e2e-selectors": "npm:^11.0.0"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.14"
|
||||
@ -3571,7 +3571,7 @@ __metadata:
|
||||
"@grafana/ui": ^10.4.1
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
checksum: 10/d99e88ba26f6df34fa595656be20cdaee8e9e59a8928e80691bea5cd9646ef6534c9929f38a5fcca1b09891a2609d4ee8e96f898799c5ec73b1993abe09b094a
|
||||
checksum: 10/814fe81537d267640cf0e4d91c1fc5805290fd6f46bbf37633edfbc8fbeae8a064a2b9540d482adb061235bd20148ddf246db6e41a8141380e98d49ca31638f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3684,6 +3684,7 @@ __metadata:
|
||||
"@storybook/react": "npm:^8.1.6"
|
||||
"@storybook/react-webpack5": "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/jest-dom": "npm:6.4.2"
|
||||
"@testing-library/react": "npm:15.0.2"
|
||||
@ -7803,6 +7804,25 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 10.0.0
|
||||
resolution: "@testing-library/dom@npm:10.0.0"
|
||||
@ -14704,7 +14724,7 @@ __metadata:
|
||||
languageName: node
|
||||
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
|
||||
resolution: "entities@npm:4.4.0"
|
||||
checksum: 10/b627cb900e901cc7817037b83bf993a1cbf6a64850540f7526af7bcf9c7d09ebc671198e6182cfae4680f733799e2852e6a1c46aa62ff36eb99680057a038df5
|
||||
@ -17883,19 +17903,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"htmlparser2@npm:^8.0.1":
|
||||
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":
|
||||
"htmlparser2@npm:^8.0.1, htmlparser2@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "htmlparser2@npm:8.0.2"
|
||||
dependencies:
|
||||
@ -24769,7 +24777,7 @@ __metadata:
|
||||
languageName: node
|
||||
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
|
||||
resolution: "punycode@npm:2.3.1"
|
||||
checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059
|
||||
@ -24783,13 +24791,6 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 6.0.3
|
||||
resolution: "pure-rand@npm:6.0.3"
|
||||
|
Loading…
Reference in New Issue
Block a user