From 070ae1af61f6c00b0d55c527bf98fbec98fdf7da Mon Sep 17 00:00:00 2001 From: Fiona Liao Date: Tue, 18 Feb 2025 15:49:51 +0000 Subject: [PATCH 01/34] Fix typo for prepare time series transformation (#100436) --- .../transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx b/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx index 2d608199c53..a38f14aea32 100644 --- a/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx +++ b/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx @@ -52,7 +52,7 @@ const longInfo = { From 6f0191403d4284b1b8c2061a92d951cd9b97d264 Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Tue, 18 Feb 2025 17:00:30 +0100 Subject: [PATCH 02/34] Chore: Increase golangci-lint timeout to 15min (#100811) --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index c4f0495f44c..35b9bd1d4ec 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,5 @@ run: - timeout: 10m + timeout: 15m concurrency: 10 allow-parallel-runners: true linters-settings: From 08000eeece615ed292c266606e1fbbe523999459 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 18 Feb 2025 17:23:32 +0100 Subject: [PATCH 03/34] Chore: refresh betterer results file (#100863) * chore(betterer): refresh results file * ci(betterer): add betterer:ci script to catch out of sync results file --- .betterer.results | 43 ++++++------------------------------ .drone.yml | 10 ++++----- package.json | 1 + scripts/drone/steps/lib.star | 2 +- 4 files changed, 14 insertions(+), 42 deletions(-) diff --git a/.betterer.results b/.betterer.results index de9d9f96881..60612fa6fa7 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1545,7 +1545,7 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx:5381": [ [0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props in text props. Wrap text with or use t() or use t()", "2"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], @@ -2467,19 +2467,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], - "public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "10"] - ], "public/app/features/alerting/unified/components/rule-viewer/tabs/Routing.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -5450,12 +5437,6 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/components/GetStartedWithPlugin/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./GetStartedWithPlugin\`)", "0"] ], - "public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] - ], "public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], @@ -6114,8 +6095,7 @@ exports[`better eslint`] = { ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -6123,15 +6103,11 @@ exports[`better eslint`] = { "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RegexMatcherEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -6314,10 +6290,7 @@ exports[`better eslint`] = { [0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "4"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "5"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] ], "public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx:5381": [ [0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinByField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], @@ -6336,9 +6309,7 @@ exports[`better eslint`] = { ], "public/app/features/transformers/editors/LimitTransformerEditor.tsx:5381": [ [0, 0, 0, "\'@grafana/data/src/transformations/transformers/limit\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/transformers/editors/MergeTransformerEditor.tsx:5381": [ [0, 0, 0, "\'@grafana/data/src/transformations/transformers/merge\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], diff --git a/.drone.yml b/.drone.yml index 1a23e2d2838..4e3b9943f49 100644 --- a/.drone.yml +++ b/.drone.yml @@ -179,7 +179,7 @@ steps: name: yarn-install - commands: - apk add --update git bash - - yarn betterer ci + - yarn betterer:ci depends_on: - yarn-install image: node:22.11.0-alpine @@ -1717,7 +1717,7 @@ steps: name: yarn-install - commands: - apk add --update git bash - - yarn betterer ci + - yarn betterer:ci depends_on: - yarn-install image: node:22.11.0-alpine @@ -2865,7 +2865,7 @@ steps: name: yarn-install - commands: - apk add --update git bash - - yarn betterer ci + - yarn betterer:ci depends_on: - yarn-install image: node:22.11.0-alpine @@ -4342,7 +4342,7 @@ steps: name: yarn-install - commands: - apk add --update git bash - - yarn betterer ci + - yarn betterer:ci depends_on: - yarn-install image: node:22.11.0-alpine @@ -5605,6 +5605,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: a1d8f3defdf52b76049d8a8df1c21dd13cac9a2a9f80566abceffb4acd1b49d2 +hmac: 7a16cbedacf8a7381304abfde88319ce712a133e24c1ab304543c4c7ac318816 ... diff --git a/package.json b/package.json index 388e189f171..949b224ab90 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "betterer": "betterer --tsconfig ./scripts/cli/tsconfig.json", "betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts", "betterer:issues": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/generateBettererIssues.ts", + "betterer:ci": "betterer ci --tsconfig ./scripts/cli/tsconfig.json", "plugin:build": "nx run-many -t build --projects='tag:scope:plugin'", "plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'", "plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100", diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 3ee633dfb06..949a6fc9242 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -611,7 +611,7 @@ def betterer_frontend_step(): ], "commands": [ "apk add --update git bash", - "yarn betterer ci", + "yarn betterer:ci", ], } From 67d8610e3140430d99e6bada225389fdb907450d Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:24:44 +0100 Subject: [PATCH 04/34] SQL data sources: Use correct error source for sql processing errors (#100872) * SQL data sources: Use correct error source for sql processing errors * Fix lint * Fix cyclomatic complexity * Nit rename variable to make it more clear what those errors are --- .../sqleng/sql_engine.go | 19 ++++++++++++++++++- pkg/tsdb/mssql/sqleng/sql_engine.go | 19 ++++++++++++++++++- pkg/tsdb/mysql/sqleng/sql_engine.go | 19 ++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go index dbf02b75b0c..7712fe7c3d5 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go @@ -235,7 +235,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG emptyFrame.SetMeta(&data.FrameMeta{ ExecutedQueryString: query, }) - if backend.IsDownstreamError(err) { + if isDownstreamError(err) { source = backend.ErrorSourceDownstream } queryResult.dataResponse.Error = fmt.Errorf("%s: %w", frameErr, err) @@ -646,3 +646,20 @@ func epochPrecisionToMS(value float64) float64 { return value } + +func isDownstreamError(err error) bool { + if backend.IsDownstreamError(err) { + return true + } + resultProcessingDownstreamErrors := []error{ + data.ErrorInputFieldsWithoutRows, + data.ErrorSeriesUnsorted, + data.ErrorNullTimeValues, + } + for _, e := range resultProcessingDownstreamErrors { + if errors.Is(err, e) { + return true + } + } + return false +} diff --git a/pkg/tsdb/mssql/sqleng/sql_engine.go b/pkg/tsdb/mssql/sqleng/sql_engine.go index 5831a778226..350c038b3c0 100644 --- a/pkg/tsdb/mssql/sqleng/sql_engine.go +++ b/pkg/tsdb/mssql/sqleng/sql_engine.go @@ -231,7 +231,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG emptyFrame.SetMeta(&data.FrameMeta{ ExecutedQueryString: query, }) - if backend.IsDownstreamError(err) { + if isDownstreamError(err) { source = backend.ErrorSourceDownstream } queryResult.dataResponse.Error = fmt.Errorf("%s: %w", frameErr, err) @@ -642,3 +642,20 @@ func epochPrecisionToMS(value float64) float64 { return value } + +func isDownstreamError(err error) bool { + if backend.IsDownstreamError(err) { + return true + } + resultProcessingDownstreamErrors := []error{ + data.ErrorInputFieldsWithoutRows, + data.ErrorSeriesUnsorted, + data.ErrorNullTimeValues, + } + for _, e := range resultProcessingDownstreamErrors { + if errors.Is(err, e) { + return true + } + } + return false +} diff --git a/pkg/tsdb/mysql/sqleng/sql_engine.go b/pkg/tsdb/mysql/sqleng/sql_engine.go index 5831a778226..350c038b3c0 100644 --- a/pkg/tsdb/mysql/sqleng/sql_engine.go +++ b/pkg/tsdb/mysql/sqleng/sql_engine.go @@ -231,7 +231,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG emptyFrame.SetMeta(&data.FrameMeta{ ExecutedQueryString: query, }) - if backend.IsDownstreamError(err) { + if isDownstreamError(err) { source = backend.ErrorSourceDownstream } queryResult.dataResponse.Error = fmt.Errorf("%s: %w", frameErr, err) @@ -642,3 +642,20 @@ func epochPrecisionToMS(value float64) float64 { return value } + +func isDownstreamError(err error) bool { + if backend.IsDownstreamError(err) { + return true + } + resultProcessingDownstreamErrors := []error{ + data.ErrorInputFieldsWithoutRows, + data.ErrorSeriesUnsorted, + data.ErrorNullTimeValues, + } + for _, e := range resultProcessingDownstreamErrors { + if errors.Is(err, e) { + return true + } + } + return false +} From fd8a2158f1f26153be60aaa9052a180329280e86 Mon Sep 17 00:00:00 2001 From: Ezequiel Victorero Date: Tue, 18 Feb 2025 14:35:40 -0300 Subject: [PATCH 05/34] Docs: Image renderer https configuration using environment variables (#100846) --- .../setup-grafana/image-rendering/_index.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/sources/setup-grafana/image-rendering/_index.md b/docs/sources/setup-grafana/image-rendering/_index.md index 1daecc33d2f..c3f1fc0425a 100644 --- a/docs/sources/setup-grafana/image-rendering/_index.md +++ b/docs/sources/setup-grafana/image-rendering/_index.md @@ -221,10 +221,14 @@ HTTPS protocol is supported in the image renderer v3.11.0 and later. Change the protocol of the server, it can be `http` or `https`. Default is `http`. +```bash +HTTP_PROTOCOL=https +``` + ```json { "service": { - "protocol": "http" + "protocol": "https" } } ``` @@ -233,6 +237,11 @@ Change the protocol of the server, it can be `http` or `https`. Default is `http Path to the image renderer certificate and key file used to start an HTTPS server. +```bash +HTTP_CERT_FILE=./path/to/cert +HTTP_CERT_KEY=./path/to/key +``` + ```json { "service": { @@ -246,6 +255,10 @@ Path to the image renderer certificate and key file used to start an HTTPS serve Minimum TLS version allowed. Accepted values are: `TLSv1.2`, `TLSv1.3`. Default is `TLSv1.2`. +```bash +HTTP_MIN_TLS_VERSION=TLSv1.2 +``` + ```json { "service": { From 7b3b165c3d724d1a6627876e1aa0714446c610d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Tue, 18 Feb 2025 19:29:26 +0100 Subject: [PATCH 06/34] Ci: Use app token during checkout (#100889) Use app token during checkout --- .github/workflows/release-pr.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 3926cdbb2cc..1234cdc75ef 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -74,6 +74,7 @@ jobs: ref: ${{ steps.branch.outputs.branch }} fetch-depth: 0 fetch-tags: true + token: ${{ steps.generate_token.outputs.token }} - name: Checkout Grafana (main) uses: actions/checkout@v4 with: @@ -81,6 +82,7 @@ jobs: fetch-depth: '0' fetch-tags: 'false' path: .grafana-main + token: ${{ steps.generate_token.outputs.token }} - name: Setup nodejs environment uses: actions/setup-node@v4 with: From 67be9aeed67cfd89516f0a3ff60c79b4830e5bf0 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 18 Feb 2025 11:30:11 -0700 Subject: [PATCH 07/34] K8s: Search fallback: Support all sort by methods (#100776) --- pkg/api/dashboard_test.go | 6 +- pkg/api/folder_bench_test.go | 7 +- .../datamigrations/to_unified_storage.go | 3 +- .../apis/dashboard/legacy/sql_dashboards.go | 4 +- .../dashboard/legacysearcher/search_client.go | 58 +++++-- .../legacysearcher/search_client_test.go | 147 +++++++++++++++++- pkg/registry/apis/dashboard/search.go | 2 +- .../apis/dashboard/v0alpha1/register.go | 6 +- .../apis/dashboard/v1alpha1/register.go | 4 +- .../apis/dashboard/v2alpha1/register.go | 4 +- pkg/server/wire.go | 2 + .../ossaccesscontrol/testutil/testutil.go | 3 +- .../accesscontrol/accesscontrol_test.go | 5 +- .../annotationsimpl/annotations_test.go | 9 +- pkg/services/apiserver/client/client.go | 5 +- .../database/database_folder_test.go | 3 +- .../dashboards/database/database_test.go | 5 +- .../dashboards/service/dashboard_service.go | 5 +- .../dashboard_service_integration_test.go | 26 +++- .../service/service_test.go | 2 + .../dashboardversion/dashverimpl/dashver.go | 4 +- pkg/services/folder/folderimpl/folder.go | 4 + pkg/services/folder/folderimpl/folder_test.go | 10 +- .../folderimpl/folder_unifiedstorage_test.go | 3 +- pkg/services/libraryelements/database.go | 4 +- .../libraryelements_get_all_test.go | 4 +- .../libraryelements/libraryelements_test.go | 14 +- .../librarypanels/librarypanels_test.go | 9 +- .../ngalert/api/api_provisioning_test.go | 3 +- .../ngalert/provisioning/alert_rules_test.go | 3 +- pkg/services/ngalert/testutil/testutil.go | 4 +- .../dashboards/file_reader_test.go | 3 +- .../provisioning/dashboards/validator_test.go | 3 +- .../publicdashboards/api/query_test.go | 3 +- .../publicdashboards/service/service_test.go | 5 +- pkg/services/quota/quotaimpl/quota_test.go | 5 +- pkg/services/search/service.go | 22 ++- pkg/services/search/{ => sort}/sorting.go | 28 +++- .../sqlstore/permissions/dashboard_test.go | 3 +- .../permissions/dashboards_bench_test.go | 3 +- .../federated/federatedtests/stats_test.go | 3 +- pkg/storage/unified/resource/document.go | 3 +- pkg/storage/unified/search/bleve.go | 6 +- 43 files changed, 356 insertions(+), 99 deletions(-) rename pkg/services/search/{ => sort}/sorting.go (58%) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 55cff3d3c13..5a066f5e8dd 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -55,6 +55,7 @@ import ( "github.com/grafana/grafana/pkg/services/publicdashboards/api" publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/star/startest" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" @@ -832,11 +833,11 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr quotaService := quotatest.New(false, nil) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) if dashboardService == nil { dashboardService, err = service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, sort.ProvideService(), ) require.NoError(t, err) dashboardService.(dashboards.PermissionsRegistrationService).RegisterDashboardPermissions(dashboardPermissions) @@ -845,6 +846,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr dashboardProvisioningService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 6ad9174981f..c940c7c30d7 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -40,6 +40,7 @@ import ( "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/star/startest" @@ -462,7 +463,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog fStore := folderimpl.ProvideStore(sc.db) folderServiceWithFlagOn := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, - nil, sc.db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sc.db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) acSvc := acimpl.ProvideOSSService( sc.cfg, acdb.ProvideService(sc.db), actionSets, localcache.ProvideService(), features, tracing.InitializeTracerForTest(), sc.db, permreg.ProvidePermissionRegistry(), nil, @@ -473,7 +474,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl( sc.cfg, dashStore, folderStore, features, folderPermissions, ac, - folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, + folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, sort.ProvideService(), ) require.NoError(b, err) @@ -490,7 +491,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog SQLStore: sc.db, Features: features, QuotaService: quotaSrv, - SearchService: search.ProvideService(sc.cfg, sc.db, starSvc, dashboardSvc, folderServiceWithFlagOn, features), + SearchService: search.ProvideService(sc.cfg, sc.db, starSvc, dashboardSvc, folderServiceWithFlagOn, features, sort.ProvideService()), folderService: folderServiceWithFlagOn, DashboardService: dashboardSvc, } diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go index a7331cda28e..164ea45cd83 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go @@ -21,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified" @@ -63,7 +64,7 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err migrator := legacy.NewDashboardAccess( legacysql.NewDatabaseProvider(sqlStore), authlib.OrgNamespaceFormatter, - nil, provisioning, false, + nil, provisioning, false, sort.ProvideService(), ) yes, err := promptYesNo(fmt.Sprintf("Count legacy resources for namespace: %s?", opts.Namespace)) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 0ba1e67cc88..5a4264611bc 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -24,6 +24,7 @@ import ( gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" @@ -68,8 +69,9 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider, dashStore dashboards.Store, provisioning provisioning.ProvisioningService, softDelete bool, + sorter sort.Service, ) DashboardAccess { - dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore) + dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore, sorter) return &dashboardSqlAccess{ sql: sql, namespacer: namespacer, diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 565d7448e1f..1e59bc38100 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -14,9 +14,10 @@ import ( folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" - "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/storage/unified/resource" + unisearch "github.com/grafana/grafana/pkg/storage/unified/search" "google.golang.org/grpc" "k8s.io/apimachinery/pkg/selection" ) @@ -24,10 +25,19 @@ import ( type DashboardSearchClient struct { resource.ResourceIndexClient dashboardStore dashboards.Store + sorter sort.Service } -func NewDashboardSearchClient(dashboardStore dashboards.Store) *DashboardSearchClient { - return &DashboardSearchClient{dashboardStore: dashboardStore} +func NewDashboardSearchClient(dashboardStore dashboards.Store, sorter sort.Service) *DashboardSearchClient { + return &DashboardSearchClient{dashboardStore: dashboardStore, sorter: sorter} +} + +var sortByMapping = map[string]string{ + unisearch.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently-", + unisearch.DASHBOARD_VIEWS_TOTAL: "viewed-", + unisearch.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently-", + unisearch.DASHBOARD_ERRORS_TOTAL: "errors-", + "title": "alpha-", } // nolint:gocyclo @@ -78,16 +88,30 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour query.Type = queryType } - // technically, there exists the ability to register multiple ways of sorting using the legacy database - // see RegisterSortOption in pkg/services/search/sorting.go - // however, it doesn't look like we are taking advantage of that. And since by default the legacy - // sql will sort by title ascending, we only really need to handle the "alpha-desc" case - if req.SortBy != nil { - for _, sort := range req.SortBy { - if sort.Field == "title" && sort.Desc { - query.Sort = search.SortAlphaDesc - } + sortByField := "" + if len(req.SortBy) != 0 { + if len(req.SortBy) > 1 { + return nil, fmt.Errorf("only one sort field is supported") } + sort := req.SortBy[0] + sortByField = strings.TrimPrefix(sort.Field, resource.SEARCH_FIELD_PREFIX) + sorterName := sortByMapping[sortByField] + + if sort.Desc { + sorterName += "desc" + } else { + sorterName += "asc" + } + + if sorter, ok := c.sorter.GetSortOption(sorterName); ok { + query.Sort = sorter + } + } + + // the title search will not return any sortMeta (an int64), like + // most sorting will. Without this, the title will be set to sortMeta (0) + if sortByField == resource.SEARCH_FIELD_TITLE { + sortByField = "" } // if searching for tags, get those instead of the dashboards or folders @@ -178,7 +202,6 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour query.ProvisionedRepo, _ = dashboard.GetProvisionedFileNameFromMeta(vals[0]) } } - searchFields := resource.StandardSearchFields() list := &resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ @@ -186,6 +209,10 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + &resource.ResourceTableColumnDefinition{ + Name: sortByField, + Type: resource.ResourceTableColumnDefinition_INT64, + }, }, }, } @@ -213,7 +240,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour Key: getResourceKey(&dashboards.DashboardSearchProjection{ UID: dashboard.UID, }, req.Options.Key.Namespace), - Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), {}}, + Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), {}, {}}, }) } @@ -235,7 +262,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{ Key: getResourceKey(dashboard, req.Options.Key.Namespace), - Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags}, + Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.SortMeta, 10))}, }) } @@ -276,6 +303,7 @@ func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards FolderUID: item.FolderUID, Tags: []string{}, IsFolder: item.IsFolder, + SortMeta: item.SortMeta, } hitList = append(hitList, hit) hits[key] = hit diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index d923e27b837..452a15f0ae5 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -3,14 +3,18 @@ package legacysearcher import ( "context" "encoding/json" + "strconv" "testing" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/dashboard" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/unified/resource" + unisearch "github.com/grafana/grafana/pkg/storage/unified/search" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/selection" @@ -18,10 +22,13 @@ import ( func TestDashboardSearchClient_Search(t *testing.T) { mockStore := dashboards.NewFakeDashboardStore(t) - client := NewDashboardSearchClient(mockStore) + sortSvc := sort.ProvideService() + client := NewDashboardSearchClient(mockStore, sortSvc) ctx := context.Background() user := &user.SignedInUser{OrgID: 2} ctx = identity.WithRequester(ctx, user) + emptyTags, err := json.Marshal([]string{}) + require.NoError(t, err) dashboardKey := &resource.ResourceKey{ Name: "uid", @@ -29,9 +36,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { } t.Run("Should parse results into GRPC", func(t *testing.T) { + sorter, _ := sortSvc.GetSortOption("alpha-asc") mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{ SignedInUser: user, // user from context should be used Type: "dash-db", // should set type based off of key + Sort: sorter, }).Return([]dashboards.DashboardSearchProjection{ {UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"}, {UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"}, @@ -41,17 +50,19 @@ func TestDashboardSearchClient_Search(t *testing.T) { Options: &resource.ListOptions{ Key: dashboardKey, }, + SortBy: []*resource.ResourceSearchRequest_Sort{ + { + Field: resource.SEARCH_FIELD_TITLE, + }, + }, } resp, err := client.Search(ctx, req) require.NoError(t, err) tags, err := json.Marshal([]string{"term"}) require.NoError(t, err) - - emptyTags, err := json.Marshal([]string{}) - require.NoError(t, err) - require.NotNil(t, resp) + searchFields := resource.StandardSearchFields() require.Equal(t, &resource.ResourceSearchResponse{ TotalHits: 2, @@ -60,6 +71,10 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + &resource.ResourceTableColumnDefinition{ + Name: "", // sort by should be empty if title is what we sorted by + Type: resource.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resource.ResourceTableRow{ { @@ -72,6 +87,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder1"), tags, + []byte(strconv.FormatInt(0, 10)), }, }, { @@ -84,6 +100,127 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard2"), []byte("folder2"), emptyTags, + []byte(strconv.FormatInt(0, 10)), + }, + }, + }, + }, + }, resp) + mockStore.AssertExpectations(t) + }) + + t.Run("Sorting should be properly parsed into legacy sorting options (asc), and results added", func(t *testing.T) { + sortOptionAsc := model.SortOption{ + Name: "viewed-asc", // should add -asc to the sort field and match on that + } + sortSvc.RegisterSortOption(sortOptionAsc) + mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{ + SignedInUser: user, + Type: "dash-db", + Sort: sortOptionAsc, + }).Return([]dashboards.DashboardSearchProjection{ + {UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)}, + }, nil).Once() + + req := &resource.ResourceSearchRequest{ + Options: &resource.ListOptions{ + Key: dashboardKey, + }, + SortBy: []*resource.ResourceSearchRequest_Sort{ + { + Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed + Desc: false, + }, + }, + } + resp, err := client.Search(ctx, req) + require.NoError(t, err) + require.NotNil(t, resp) + searchFields := resource.StandardSearchFields() + require.Equal(t, &resource.ResourceSearchResponse{ + TotalHits: 1, + Results: &resource.ResourceTable{ + Columns: []*resource.ResourceTableColumnDefinition{ + searchFields.Field(resource.SEARCH_FIELD_TITLE), + searchFields.Field(resource.SEARCH_FIELD_FOLDER), + searchFields.Field(resource.SEARCH_FIELD_TAGS), + &resource.ResourceTableColumnDefinition{ + Name: "views_total", + Type: resource.ResourceTableColumnDefinition_INT64, + }, + }, + Rows: []*resource.ResourceTableRow{ + { + Key: &resource.ResourceKey{ + Name: "uid", + Group: dashboard.GROUP, + Resource: dashboard.DASHBOARD_RESOURCE, + }, + Cells: [][]byte{ + []byte("Test Dashboard"), + []byte("folder"), + emptyTags, + []byte(strconv.FormatInt(50, 10)), + }, + }, + }, + }, + }, resp) + mockStore.AssertExpectations(t) + }) + + t.Run("Sorting should be properly parsed into legacy sorting options (desc)", func(t *testing.T) { + sortOptionAsc := model.SortOption{ + Name: "errors-recently-desc", // should add -asc to the sort field and match on that + } + sortSvc.RegisterSortOption(sortOptionAsc) + mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{ + SignedInUser: user, + Type: "dash-db", + Sort: sortOptionAsc, + }).Return([]dashboards.DashboardSearchProjection{ + {UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)}, + }, nil).Once() + + req := &resource.ResourceSearchRequest{ + Options: &resource.ListOptions{ + Key: dashboardKey, + }, + SortBy: []*resource.ResourceSearchRequest_Sort{ + { + Field: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS, + Desc: true, + }, + }, + } + resp, err := client.Search(ctx, req) + require.NoError(t, err) + require.NotNil(t, resp) + searchFields := resource.StandardSearchFields() + require.Equal(t, &resource.ResourceSearchResponse{ + TotalHits: 1, + Results: &resource.ResourceTable{ + Columns: []*resource.ResourceTableColumnDefinition{ + searchFields.Field(resource.SEARCH_FIELD_TITLE), + searchFields.Field(resource.SEARCH_FIELD_FOLDER), + searchFields.Field(resource.SEARCH_FIELD_TAGS), + &resource.ResourceTableColumnDefinition{ + Name: "errors_last_30_days", + Type: resource.ResourceTableColumnDefinition_INT64, + }, + }, + Rows: []*resource.ResourceTableRow{ + { + Key: &resource.ResourceKey{ + Name: "uid", + Group: dashboard.GROUP, + Resource: dashboard.DASHBOARD_RESOURCE, + }, + Cells: [][]byte{ + []byte("Test Dashboard"), + []byte("folder"), + emptyTags, + []byte(strconv.FormatInt(2, 10)), }, }, }, diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 7dec39f22ee..0db9204d055 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -290,7 +290,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { if queryParams.Has("sort") { for _, sort := range queryParams["sort"] { if slices.Contains(search.DashboardFields(), sort) { - sort = "fields." + sort + sort = resource.SEARCH_FIELD_PREFIX + sort } s := &resource.ResourceSearchRequest_Sort{Field: sort} if strings.HasPrefix(sort, "-") { diff --git a/pkg/registry/apis/dashboard/v0alpha1/register.go b/pkg/registry/apis/dashboard/v0alpha1/register.go index 815f32815ab..c9dda64fa21 100644 --- a/pkg/registry/apis/dashboard/v0alpha1/register.go +++ b/pkg/registry/apis/dashboard/v0alpha1/register.go @@ -31,6 +31,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified/apistore" @@ -68,11 +69,12 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, sql db.DB, tracing *tracing.TracingService, unified resource.ResourceClient, + sorter sort.Service, ) *DashboardsAPIBuilder { softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) dbp := legacysql.NewDatabaseProvider(sql) namespacer := request.GetNamespaceMapper(cfg) - legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore) + legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter) builder := &DashboardsAPIBuilder{ log: log.New("grafana-apiserver.dashboards.v0alpha1"), DashboardsAPIBuilder: dashboard.DashboardsAPIBuilder{ @@ -86,7 +88,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, legacy: &dashboard.DashboardStorage{ Resource: dashboardv0alpha1.DashboardResourceInfo, - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete), + Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), TableConverter: dashboardv0alpha1.DashboardResourceInfo.TableConverter(), Features: features, }, diff --git a/pkg/registry/apis/dashboard/v1alpha1/register.go b/pkg/registry/apis/dashboard/v1alpha1/register.go index 5c73464fa9d..f64cdb45c52 100644 --- a/pkg/registry/apis/dashboard/v1alpha1/register.go +++ b/pkg/registry/apis/dashboard/v1alpha1/register.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified/apistore" @@ -65,6 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, sql db.DB, tracing *tracing.TracingService, unified resource.ResourceClient, + sorter sort.Service, ) *DashboardsAPIBuilder { softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) dbp := legacysql.NewDatabaseProvider(sql) @@ -81,7 +83,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, legacy: &dashboard.DashboardStorage{ Resource: dashboardv1alpha1.DashboardResourceInfo, - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete), + Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), TableConverter: dashboardv1alpha1.DashboardResourceInfo.TableConverter(), Features: features, }, diff --git a/pkg/registry/apis/dashboard/v2alpha1/register.go b/pkg/registry/apis/dashboard/v2alpha1/register.go index f7bacc0afc6..56767e6562b 100644 --- a/pkg/registry/apis/dashboard/v2alpha1/register.go +++ b/pkg/registry/apis/dashboard/v2alpha1/register.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified/apistore" @@ -65,6 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, sql db.DB, tracing *tracing.TracingService, unified resource.ResourceClient, + sorter sort.Service, ) *DashboardsAPIBuilder { softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) dbp := legacysql.NewDatabaseProvider(sql) @@ -82,7 +84,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, legacy: &dashboard.DashboardStorage{ Resource: dashboardv2alpha1.DashboardResourceInfo, - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete), + Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter), TableConverter: dashboardv2alpha1.DashboardResourceInfo.TableConverter(), Features: features, }, diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 4525788869d..41e5c6a9fd3 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -120,6 +120,7 @@ import ( "github.com/grafana/grafana/pkg/services/quota/quotaimpl" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/secrets" secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database" @@ -232,6 +233,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, + sort.ProvideService, search.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, diff --git a/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go b/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go index 3af111020d4..96f51c22d0b 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/services/licensing/licensingtest" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" @@ -47,7 +48,7 @@ func ProvideFolderPermissions( folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) fService := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) acSvc := acimpl.ProvideOSSService( cfg, acdb.ProvideService(sqlStore), actionSets, localcache.ProvideService(), diff --git a/pkg/services/annotations/accesscontrol/accesscontrol_test.go b/pkg/services/annotations/accesscontrol/accesscontrol_test.go index da8f266e6cf..cf91f396516 100644 --- a/pkg/services/annotations/accesscontrol/accesscontrol_test.go +++ b/pkg/services/annotations/accesscontrol/accesscontrol_test.go @@ -24,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -50,9 +51,9 @@ func TestIntegrationAuthorize(t *testing.T) { ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) folderSvc := folderimpl.ProvideService( fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, - nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, sort.ProvideService()) require.NoError(t, err) dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 6f08803bd64..a6adc9a60c5 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" alertingStore "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -62,9 +63,9 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) folderSvc := folderimpl.ProvideService( fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, - nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, sort.ProvideService()) require.NoError(t, err) dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) repo := ProvideService(sql, cfg, features, tagService, tracing.InitializeTracerForTest(), ruleStore, dashSvc) @@ -245,9 +246,9 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { folderStore := folderimpl.ProvideDashboardFolderStore(sql) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, - nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, sort.ProvideService()) require.NoError(t, err) dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) cfg.AnnotationMaximumTagsLength = 60 diff --git a/pkg/services/apiserver/client/client.go b/pkg/services/apiserver/client/client.go index 67fc5f1c48f..b6902db71d6 100644 --- a/pkg/services/apiserver/client/client.go +++ b/pkg/services/apiserver/client/client.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -51,8 +52,8 @@ type k8sHandler struct { } func NewK8sHandler(cfg *setting.Cfg, namespacer request.NamespaceMapper, gvr schema.GroupVersionResource, - restConfig func(context.Context) *rest.Config, dashStore dashboards.Store, userSvc user.Service, resourceClient resource.ResourceClient) K8sHandler { - legacySearcher := legacysearcher.NewDashboardSearchClient(dashStore) + restConfig func(context.Context) *rest.Config, dashStore dashboards.Store, userSvc user.Service, resourceClient resource.ResourceClient, sorter sort.Service) K8sHandler { + legacySearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter) key := gvr.Resource + "." + gvr.Group // the unified storage key in the config.ini is resource + group searchClient := resource.NewSearchClient(cfg, key, resourceClient, legacySearcher) diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index 579325ca285..7a3d2c082fa 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -302,7 +303,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { folderStore := folderimpl.ProvideStore(sqlStore) folderSvc := folderimpl.ProvideService( folderStore, mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) parentUID := "" for i := 0; ; i++ { diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 3cddfb9c622..bccba253fb6 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" @@ -928,7 +929,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) { fStore := folderimpl.ProvideStore(sqlStore) folderServiceWithFlagOn := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) user := &user.SignedInUser{ OrgID: 1, @@ -1048,7 +1049,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) { folderServiceWithFlagOn := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) user := &user.SignedInUser{ OrgID: 1, diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index f8ca348421e..552e9805e52 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -49,6 +49,7 @@ import ( "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/user" @@ -95,9 +96,9 @@ func ProvideDashboardServiceImpl( ac accesscontrol.AccessControl, folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer, restConfigProvider apiserver.RestConfigProvider, userService user.Service, quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper, - resourceClient resource.ResourceClient, + resourceClient resource.ResourceClient, sorter sort.Service, ) (*DashboardServiceImpl, error) { - k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient) + k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient, sorter) dashSvc := &DashboardServiceImpl{ cfg: cfg, diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 7d91fafba95..b234ca2eaf7 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -27,6 +27,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -896,7 +897,9 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc cfg, nil, tracer, - nil) + nil, + sort.ProvideService(), + ) dashboardPermissions := accesscontrolmock.NewMockedPermissionsService() dashboardService, err := ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, @@ -912,6 +915,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc nil, nil, nil, + sort.ProvideService(), ) dashboardService.RegisterDashboardPermissions(dashboardPermissions) require.NoError(t, err) @@ -984,7 +988,9 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt cfg, nil, tracer, - nil) + nil, + sort.ProvideService(), + ) dashboardPermissions := accesscontrolmock.NewMockedPermissionsService() dashboardPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) @@ -1002,6 +1008,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashboardPermissions) @@ -1035,7 +1042,9 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto cfg, nil, tracer, - nil) + nil, + sort.ProvideService(), + ) service, err := ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, featuremgmt.WithFeatures(), @@ -1050,6 +1059,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) service.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) @@ -1102,7 +1112,9 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string cfg, nil, tracer, - nil) + nil, + sort.ProvideService(), + ) service, err := ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, @@ -1117,6 +1129,7 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashboardPermissions) @@ -1175,7 +1188,9 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da cfg, nil, tracer, - nil) + nil, + sort.ProvideService(), + ) folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) service, err := ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, @@ -1191,6 +1206,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) service.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService()) diff --git a/pkg/services/dashboardsnapshots/service/service_test.go b/pkg/services/dashboardsnapshots/service/service_test.go index 14558d2fcdc..5b9452b49f1 100644 --- a/pkg/services/dashboardsnapshots/service/service_test.go +++ b/pkg/services/dashboardsnapshots/service/service_test.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/secrets/database" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/tag/tagimpl" @@ -117,6 +118,7 @@ func TestValidateDashboardExists(t *testing.T) { nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) s := ProvideService(dsStore, secretsService, dashSvc) diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index 7cd929b26f2..475e57413be 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -21,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -41,7 +42,7 @@ type Service struct { } func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, features featuremgmt.FeatureToggles, - restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient) dashver.Service { + restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient, sorter sort.Service) dashver.Service { return &Service{ cfg: cfg, store: &sqlStore{ @@ -57,6 +58,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.Dash dashboardStore, userService, unified, + sorter, ), dashSvc: dashboardService, log: log.New("dashboard-version"), diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 7b4561fabee..119d84bc940 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -37,6 +37,7 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" @@ -87,6 +88,7 @@ func ProvideService( r prometheus.Registerer, tracer tracing.Tracer, resourceClient resource.ResourceClient, + sorter sort.Service, ) *Service { srv := &Service{ log: slog.Default().With("logger", "folder-service"), @@ -118,6 +120,7 @@ func ProvideService( dashboardStore, userService, resourceClient, + sorter, ) unifiedStore := ProvideUnifiedStore(k8sHandler, userService) @@ -135,6 +138,7 @@ func ProvideService( dashboardStore, userService, resourceClient, + sorter, ) srv.dashboardK8sClient = dashHandler } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 2d01db69950..a27f9b8d42d 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -44,6 +44,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" @@ -67,7 +68,7 @@ func TestIntegrationProvideFolderService(t *testing.T) { store := ProvideStore(db) ProvideService( store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), - nil, nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2) }) @@ -495,7 +496,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { }) publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil) + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, sort.ProvideService()) require.NoError(t, err) dashSrv.RegisterDashboardPermissions(dashboardPermissions) @@ -581,7 +582,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff, - folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil) + folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, sort.ProvideService()) require.NoError(t, err) dashSrv.RegisterDashboardPermissions(dashboardPermissions) alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b) @@ -724,7 +725,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { tc.service.store = nestedFolderStore publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil) + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, sort.ProvideService()) require.NoError(t, err) dashSrv.RegisterDashboardPermissions(dashboardPermissions) @@ -1517,6 +1518,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) dashboardService.RegisterDashboardPermissions(dashboardPermissions) diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go index 26d7941f95e..5711f4aa713 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go @@ -36,6 +36,7 @@ import ( ngstore "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" @@ -182,7 +183,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { features := featuremgmt.WithFeatures(featuresArr...) dashboardStore := dashboards.NewFakeDashboardStore(t) - k8sCli := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), v0alpha1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService, nil) + k8sCli := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), v0alpha1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService, nil, sort.ProvideService()) unifiedStore := ProvideUnifiedStore(k8sCli, userService) ctx := context.Background() diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index d133c36e650..fef92e60225 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -19,7 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/setting" @@ -468,7 +468,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI if err := folderFilter.writeFolderFilterSQL(false, &builder); err != nil { return err } - if query.SortDirection == search.SortAlphaDesc.Name { + if query.SortDirection == sort.SortAlphaDesc.Name { builder.Write(" ORDER BY 1 DESC") } else { builder.Write(" ORDER BY 1 ASC") diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index 5be5ff4dede..e3e3fab0391 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/kinds/librarypanel" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/search/sort" ) func TestGetAllLibraryElements(t *testing.T) { @@ -281,7 +281,7 @@ func TestGetAllLibraryElements(t *testing.T) { err := sc.reqContext.Req.ParseForm() require.NoError(t, err) - sc.reqContext.Req.Form.Add("sortDirection", search.SortAlphaDesc.Name) + sc.reqContext.Req.Form.Add("sortDirection", sort.SortAlphaDesc.Name) resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index bef38a44ce8..47b0137a6eb 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -41,6 +41,7 @@ import ( "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -347,7 +348,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash fStore := folderimpl.ProvideStore(sqlStore) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) _, err = folderSvc.Create(context.Background(), &folder.CreateFolderCommand{UID: folderUID, SignedInUser: &user, Title: folderUID + "-title"}) require.NoError(t, err) service, err := dashboardservice.ProvideDashboardServiceImpl( @@ -362,6 +363,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash nil, nil, nil, + sort.ProvideService(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashboardPermissions) @@ -385,7 +387,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string, folderSvc fold store := folderimpl.ProvideStore(sc.sqlStore) folderSvc = folderimpl.ProvideService( store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) t.Logf("Creating folder with title %q and UID uid_for_%s", title, title) } ctx := identity.WithRequester(context.Background(), &sc.user) @@ -449,12 +451,12 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena fStore := folderimpl.ProvideStore(sqlStore) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, folderSvc, fStore, - nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, sort.ProvideService(), ) require.NoError(t, svcErr) dashboardService.RegisterDashboardPermissions(dashboardPermissions) @@ -518,7 +520,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo publicDash.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), publicDash, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), publicDash, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) alertStore, err := ngstore.ProvideDBStore(cfg, features, sqlStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest())) require.NoError(t, err) err = folderSvc.RegisterService(alertStore) @@ -527,7 +529,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo cfg, dashboardStore, folderStore, features, folderPermissions, ac, folderSvc, fStore, - nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, sort.ProvideService(), ) require.NoError(t, dashSvcErr) dashService.RegisterDashboardPermissions(dashboardPermissions) diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 25e3a3f3108..79e2cb87171 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -736,7 +737,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash cfg, dashboardStore, folderStore, features, acmock.NewMockedPermissionsService(), ac, foldertest.NewFakeService(), folder.NewFakeStore(), - nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, sort.ProvideService(), ) require.NoError(t, err) service.RegisterDashboardPermissions(dashPermissionService) @@ -758,7 +759,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder fStore := folderimpl.ProvideStore(sc.sqlStore) s := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) t.Logf("Creating folder with title and UID %q", title) ctx := identity.WithRequester(context.Background(), sc.user) @@ -834,7 +835,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo cfg, dashStore, folderStore, features, acmock.NewMockedPermissionsService(), ac, folderSvc, folder.NewFakeStore(), - nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, sort.ProvideService(), ) require.NoError(t, err) dashService.RegisterDashboardPermissions(dashPermissionService) @@ -846,7 +847,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo folderService := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, features, ac, dashService) service := LibraryPanelService{ diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 895c4940342..dce6d5ba170 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -44,6 +44,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/secrets" secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" @@ -1921,7 +1922,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment { fStore := folderimpl.ProvideStore(sqlStore) folderService := folderimpl.ProvideService( fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, - nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) store := store.DBstore{ Logger: log, SQLStore: sqlStore, diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go index 230d1480345..a580938aa0a 100644 --- a/pkg/services/ngalert/provisioning/alert_rules_test.go +++ b/pkg/services/ngalert/provisioning/alert_rules_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" @@ -1606,7 +1607,7 @@ func TestProvisiongWithFullpath(t *testing.T) { fStore := folderimpl.ProvideStore(sqlStore) folderService := folderimpl.ProvideService( fStore, ac, inProcBus, dashboardStore, folderStore, - nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) ruleService := createAlertRuleService(t, folderService) var orgID int64 = 1 diff --git a/pkg/services/ngalert/testutil/testutil.go b/pkg/services/ngalert/testutil/testutil.go index b9e4e19ba99..739274af1bd 100644 --- a/pkg/services/ngalert/testutil/testutil.go +++ b/pkg/services/ngalert/testutil/testutil.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/setting" @@ -32,7 +33,7 @@ func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStor tb.Helper() fStore := folderimpl.ProvideStore(db) return folderimpl.ProvideService(fStore, ac, bus, dashboardStore, folderStore, nil, db, - features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) } func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) { @@ -64,6 +65,7 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa features, folderPermissions, ac, foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + sort.ProvideService(), ) require.NoError(tb, err) dashboardService.RegisterDashboardPermissions(dashboardPermissions) diff --git a/pkg/services/provisioning/dashboards/file_reader_test.go b/pkg/services/provisioning/dashboards/file_reader_test.go index 4be2d2c01a1..83e81df193b 100644 --- a/pkg/services/provisioning/dashboards/file_reader_test.go +++ b/pkg/services/provisioning/dashboards/file_reader_test.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/tests/testsuite" @@ -131,7 +132,7 @@ func TestDashboardFileReader(t *testing.T) { folderStore := folderimpl.ProvideDashboardFolderStore(sql) folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), - supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil) + supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) t.Run("Reading dashboards from disk", func(t *testing.T) { t.Run("Can read default dashboard", func(t *testing.T) { diff --git a/pkg/services/provisioning/dashboards/validator_test.go b/pkg/services/provisioning/dashboards/validator_test.go index 86026f5dd62..212ebb696ce 100644 --- a/pkg/services/provisioning/dashboards/validator_test.go +++ b/pkg/services/provisioning/dashboards/validator_test.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" + grafanasort "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" ) @@ -50,7 +51,7 @@ func TestDuplicatesValidator(t *testing.T) { folderStore := folderimpl.ProvideDashboardFolderStore(sql) folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), - supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil) + supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, grafanasort.ProvideService()) t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) { const folderName = "duplicates-validator-folder" diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index 6739d9f1db4..8686cb7cba8 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -42,6 +42,7 @@ import ( . "github.com/grafana/grafana/pkg/services/publicdashboards/models" publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/web" @@ -327,7 +328,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) cfg, dashboardStoreService, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac, foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, - nil, + nil, sort.ProvideService(), ) require.NoError(t, err) dashService.RegisterDashboardPermissions(dashPermissionService) diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index 9328ff3ac3d..bf76ce9e802 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2" "github.com/grafana/grafana/pkg/services/publicdashboards/validation" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -1397,9 +1398,9 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) { folderStore := folderimpl.ProvideDashboardFolderStore(testDB) folderSvc := folderimpl.ProvideService( fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, - nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) - dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil) + dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, sort.ProvideService()) require.NoError(t, err) dashboardService.RegisterDashboardPermissions(&actest.FakePermissionsService{}) fakeGuardian := &guardian.FakeDashboardGuardian{ diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index 5ead693e8b8..d8cbef7d451 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -43,6 +43,7 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/secrets/fakes" secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager" @@ -495,9 +496,9 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) folderSvc := folderimpl.ProvideService( fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, - nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil) + ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, sort.ProvideService()) require.NoError(t, err) dashService.RegisterDashboardPermissions(acmock.NewMockedPermissionsService()) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) diff --git a/pkg/services/search/service.go b/pkg/services/search/service.go index 137cf29db12..c2241f5fce3 100644 --- a/pkg/services/search/service.go +++ b/pkg/services/search/service.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/search/model" + grafanasort "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -19,18 +20,15 @@ import ( var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/search") -func ProvideService(cfg *setting.Cfg, sqlstore db.DB, starService star.Service, dashboardService dashboards.DashboardService, folderService folder.Service, features featuremgmt.FeatureToggles) *SearchService { +func ProvideService(cfg *setting.Cfg, sqlstore db.DB, starService star.Service, dashboardService dashboards.DashboardService, folderService folder.Service, features featuremgmt.FeatureToggles, sortService grafanasort.Service) *SearchService { s := &SearchService{ - Cfg: cfg, - sortOptions: map[string]model.SortOption{ - SortAlphaAsc.Name: SortAlphaAsc, - SortAlphaDesc.Name: SortAlphaDesc, - }, + Cfg: cfg, sqlstore: sqlstore, starService: starService, folderService: folderService, features: features, dashboardService: dashboardService, + sortService: sortService, } return s } @@ -61,7 +59,7 @@ type Service interface { type SearchService struct { Cfg *setting.Cfg - sortOptions map[string]model.SortOption + sortService grafanasort.Service sqlstore db.DB starService star.Service dashboardService dashboards.DashboardService @@ -109,7 +107,7 @@ func (s *SearchService) SearchHandler(ctx context.Context, query *Query) (model. IsDeleted: query.IsDeleted, } - if sortOpt, exists := s.sortOptions[query.Sort]; exists { + if sortOpt, exists := s.sortService.GetSortOption(query.Sort); exists { dashboardQuery.Sort = sortOpt } @@ -154,3 +152,11 @@ func sortedHits(unsorted model.HitList) model.HitList { return hits } + +func (s *SearchService) RegisterSortOption(option model.SortOption) { + s.sortService.RegisterSortOption(option) +} + +func (s *SearchService) SortOptions() []model.SortOption { + return s.sortService.SortOptions() +} diff --git a/pkg/services/search/sorting.go b/pkg/services/search/sort/sorting.go similarity index 58% rename from pkg/services/search/sorting.go rename to pkg/services/search/sort/sorting.go index 1749e23fa14..d9eeb87f594 100644 --- a/pkg/services/search/sorting.go +++ b/pkg/services/search/sort/sorting.go @@ -1,4 +1,4 @@ -package search +package sort import ( "sort" @@ -7,6 +7,10 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" ) +// sort is separated into its own service to allow the dashboard service to use it in the k8s +// fallback (see pkg/registry/apis/dashboard/legacysearcher/search_client.go), since search +// has a direct dependency on the dashboard service (and thus would create a circular dependency in wire) + var ( SortAlphaAsc = model.SortOption{ Name: "alpha-asc", @@ -28,13 +32,26 @@ var ( } ) +type Service struct { + sortOptions map[string]model.SortOption +} + +func ProvideService() Service { + return Service{ + sortOptions: map[string]model.SortOption{ + SortAlphaAsc.Name: SortAlphaAsc, + SortAlphaDesc.Name: SortAlphaDesc, + }, + } +} + // RegisterSortOption allows for hooking in more search options from // other services. -func (s *SearchService) RegisterSortOption(option model.SortOption) { +func (s *Service) RegisterSortOption(option model.SortOption) { s.sortOptions[option.Name] = option } -func (s *SearchService) SortOptions() []model.SortOption { +func (s *Service) SortOptions() []model.SortOption { opts := make([]model.SortOption, 0, len(s.sortOptions)) for _, o := range s.sortOptions { opts = append(opts, o) @@ -44,3 +61,8 @@ func (s *SearchService) SortOptions() []model.SortOption { }) return opts } + +func (s *Service) GetSortOption(sort string) (model.SortOption, bool) { + option, ok := s.sortOptions[sort] + return option, ok +} diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index 006ae7eb102..c20b9b21178 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" @@ -824,7 +825,7 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol fStore := folderimpl.ProvideStore(db) folderSvc := folderimpl.ProvideService( fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db), - nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) // create parent folder parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ diff --git a/pkg/services/sqlstore/permissions/dashboards_bench_test.go b/pkg/services/sqlstore/permissions/dashboards_bench_test.go index 1a9228666e4..e2ad8497d90 100644 --- a/pkg/services/sqlstore/permissions/dashboards_bench_test.go +++ b/pkg/services/sqlstore/permissions/dashboards_bench_test.go @@ -24,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" @@ -81,7 +82,7 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe fStore := folderimpl.ProvideStore(store) folderSvc := folderimpl.ProvideService( fStore, mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), - nil, store, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, store, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) origNewGuardian := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) diff --git a/pkg/storage/unified/federated/federatedtests/stats_test.go b/pkg/storage/unified/federated/federatedtests/stats_test.go index 6249de47afe..744fda91592 100644 --- a/pkg/storage/unified/federated/federatedtests/stats_test.go +++ b/pkg/storage/unified/federated/federatedtests/stats_test.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store" + "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" @@ -52,7 +53,7 @@ func TestDirectSQLStats(t *testing.T) { fStore := folderimpl.ProvideStore(db) folderSvc := folderimpl.ProvideService( fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db), - nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil) + nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, sort.ProvideService()) // create parent folder diff --git a/pkg/storage/unified/resource/document.go b/pkg/storage/unified/resource/document.go index a868719263d..4d0682c09d7 100644 --- a/pkg/storage/unified/resource/document.go +++ b/pkg/storage/unified/resource/document.go @@ -237,7 +237,7 @@ func (x *searchableDocumentFields) Fields() []string { } func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefinition { - name = strings.TrimPrefix(name, "fields.") + name = strings.TrimPrefix(name, SEARCH_FIELD_PREFIX) f, ok := x.fields[name] if ok { @@ -246,6 +246,7 @@ func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefini return nil } +const SEARCH_FIELD_PREFIX = "fields." const SEARCH_FIELD_ID = "_id" // {namespace}/{group}/{resource}/{name} const SEARCH_FIELD_KIND = "kind" // resource ( for federated index filtering ) const SEARCH_FIELD_GROUP_RESOURCE = "gr" // group/resource diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 185e0388cd4..3e5264d4d91 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -552,7 +552,7 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resource.Res fields := make([]string, 0, len(req.Fields)) for _, f := range req.Fields { if slices.Contains(DashboardFields(), f) { - f = "fields." + f + f = resource.SEARCH_FIELD_PREFIX + f } fields = append(fields, f) } @@ -677,7 +677,7 @@ func getSortFields(req *resource.ResourceSearchRequest) []string { } if slices.Contains(DashboardFields(), input) { - input = "fields." + input + input = resource.SEARCH_FIELD_PREFIX + input } if sort.Desc { @@ -847,7 +847,7 @@ func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hit v := match.Fields[fieldName] // fields that are specific to the resource get stored as fields., so we need to check for that if v == nil { - v = match.Fields["fields."+fieldName] + v = match.Fields[resource.SEARCH_FIELD_PREFIX+fieldName] } if v != nil { // Encode the value to protobuf From 2bd63a6d71f754c590c462a9c97c84779c3f0f7f Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 12:36:25 -0700 Subject: [PATCH 08/34] Release: update changelog for 10.4.16 (#100890) Update changelog Co-authored-by: github-actions[bot] --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 024f80e0ecf..1695dbdd8c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ + + +# 10.4.16 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#99920](https://github.com/grafana/grafana/pull/99920), [@DanCech](https://github.com/DanCech) + +### Bug fixes + +- **Dashboard:** Fix for overwriting an edited dashboard in the old architecture [#100288](https://github.com/grafana/grafana/pull/100288), [@bfmatei](https://github.com/bfmatei) + + # 11.5.1 (2025-02-03) From 10eb1d9062e0b8d4988e6260b8635662d3611f02 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:02:17 -0700 Subject: [PATCH 09/34] Release: update changelog for 11.0.11 (#100895) Update changelog Co-authored-by: github-actions[bot] --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1695dbdd8c0..956a074f98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ + + +# 11.0.11 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#100730](https://github.com/grafana/grafana/pull/100730), [@DanCech](https://github.com/DanCech) + +### Bug fixes + +- **Azure:** Correctly set application insights resource values [#99594](https://github.com/grafana/grafana/pull/99594), [@aangelisc](https://github.com/aangelisc) + + # 10.4.16 (2025-02-18) From 14477a7fe9c11cbbd63dc6532d08f621ce27aefa Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:34:21 -0600 Subject: [PATCH 10/34] CI: release comms should trigger on merges to release- branches (#100901) release comms should trigger on merges to release- branches --- .github/workflows/release-comms.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-comms.yml b/.github/workflows/release-comms.yml index fbc7d94ec13..86e698df758 100644 --- a/.github/workflows/release-comms.yml +++ b/.github/workflows/release-comms.yml @@ -19,7 +19,7 @@ on: - closed branches: - 'main' - - 'v*.*.*' + - 'release-*.*.*' jobs: setup: From 66932600ecfe10b77ef9ec998ed191992651003c Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 19 Feb 2025 00:49:54 +0300 Subject: [PATCH 11/34] K8s: DecisionNoOpinion for claims.TypeAnonymous (#100880) --- pkg/services/apiserver/auth/authorizer/org_id.go | 5 +++++ pkg/services/apiserver/auth/authorizer/stack_id.go | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/services/apiserver/auth/authorizer/org_id.go b/pkg/services/apiserver/auth/authorizer/org_id.go index 87b38d2f9c2..a76f55e73d7 100644 --- a/pkg/services/apiserver/auth/authorizer/org_id.go +++ b/pkg/services/apiserver/auth/authorizer/org_id.go @@ -60,6 +60,11 @@ func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut return authorizer.DecisionNoOpinion, "", nil } + // If we have an anonymous user, let the next authorizers decide. + if signedInUser.GetIdentityType() == claims.TypeAnonymous { + return authorizer.DecisionNoOpinion, "", nil + } + // Check if the user has access to the specified org // nolint:staticcheck userId, err := signedInUser.GetInternalID() diff --git a/pkg/services/apiserver/auth/authorizer/stack_id.go b/pkg/services/apiserver/auth/authorizer/stack_id.go index cbec3b0d09f..2232335984d 100644 --- a/pkg/services/apiserver/auth/authorizer/stack_id.go +++ b/pkg/services/apiserver/auth/authorizer/stack_id.go @@ -37,6 +37,11 @@ func (auth stackIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attrib return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil } + // If we have an anonymous user, let the next authorizers decide. + if signedInUser.GetIdentityType() == claims.TypeAnonymous { + return authorizer.DecisionNoOpinion, "", nil + } + info, err := claims.ParseNamespace(a.GetNamespace()) if err != nil { return authorizer.DecisionDeny, fmt.Sprintf("error reading namespace: %v", err), nil @@ -46,9 +51,9 @@ func (auth stackIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attrib if info.Value == "" { return authorizer.DecisionNoOpinion, "", nil } - if info.StackID != auth.stackID { - return authorizer.DecisionDeny, "wrong stack id is selected", nil + msg := fmt.Sprintf("wrong stack id is selected (expected: %d, found %d)", auth.stackID, info.StackID) + return authorizer.DecisionDeny, msg, nil } if info.OrgID != 1 { return authorizer.DecisionDeny, "cloud instance requires org 1", nil From e1c1cc34af160178392762b7274d46659478afd2 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:50:13 +0100 Subject: [PATCH 12/34] Alerting docs: Update `Amazon SNS` integration (#100754) * Update `Configure Amazon SNS notifications` * Clarify templateable fields * correct prop description --- .../integrations/configure-amazon-sns.md | 186 +++++++----------- 1 file changed, 76 insertions(+), 110 deletions(-) diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md index 84274e64043..9c449640511 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md @@ -14,11 +14,32 @@ labels: menuTitle: Amazon SNS title: Configure Amazon SNS for Alerting weight: 100 +refs: + notification-templates: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/template-notifications/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/ + configure-contact-points: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/manage-contact-points/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/ + test-contact-point: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/manage-contact-points/#test-a-contact-point + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/#test-a-contact-point + enable-contact-point-notifications: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/manage-contact-points/#enable-notifications-for-a-contact-point + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/#enable-notifications-for-a-contact-point --- -# Configure Amazon SNS for Alerting +# Configure Amazon SNS notifications -Use the Grafana Alerting - Amazon SNS integration to send notifications to Amazon SNS when your alerts are firing. You can receive notifications via the various subscriber channels supported by SNS. +Use the Amazon SNS integration in a contact point to send alert notifications to a SNS topic. Then, configure the SNS topic to forward notifications to distinct subscriber channels used in your SNS account. ## Before you begin @@ -27,71 +48,51 @@ Before you begin, ensure you have the following: - **AWS SNS Topic**: An SNS topic to send notifications to. - **AWS IAM Identity with necessary access**: An IAM identity (e.g. user, role) with the necessary permissions to publish messages to the SNS topic. -For an example setup, see [Example Minimal Setup Using Assumed IAM Role]({{< relref "#example-minimal-setup-using-assumed-iam-role" >}}). +For a minimal setup, refer to [Example using an Access Key](#example-using-an-access-key). -## Adding the SNS Contact Point in Grafana +## Configure Amazon SNS for a contact point -With AWS resources configured, proceed to add SNS as a contact point in Grafana. +To create a contact point with a SNS integration, complete the following steps. -- Navigate to **Alerts & IRM** -> **Alerting** -> **Contact points**. -- Click on **"Add contact point"**. -- **Name**: Enter a descriptive name (e.g., `AWS SNS`). -- Choose **"AWS SNS"** from the list of contact point types. +1. Navigate to **Alerts & IRM** -> **Alerting** -> **Contact points**. +1. Click **+ Add contact point**. +1. Enter a name for the contact point. +1. From the **Integration** list, select **AWS SNS**. +1. Set up the required [settings](#sns-settings) for your SNS configuration. +1. Click **Save contact point**. -### 2. Configure SNS Settings +For more details on contact points, including how to test them and enable notifications, refer to [Configure contact points](ref:configure-contact-points). -#### SNS Settings +## SNS Settings - **The Amazon SNS API URL**: (Optional) The SNS API URL, e.g., `https://sns.us-east-2.amazonaws.com`. If not specified, the SNS API URL from the SNS SDK will be used. - **Signature Version (sigv4)**: Configures AWS's Signature Verification 4 signing process to sign requests. - **Region**: (Optional) The AWS region. If blank, the region from the default credentials chain is used. - - **Access Key**: The AWS API access key. - - **Secret Key**: The AWS API secret key. + - **Access Key** : (Optional) The AWS API access key. + - **Secret Key**: (Optional) The AWS API secret key. + > Both `Access Key` and `Secret Key` must be provided together or left blank together. + > + > If left blank, Grafana searches for credentials using the default credentials chain, including environment variables (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`), the shared credential file, and EC2 instance roles. - **Profile**: (Optional) Named AWS profile used to authenticate. - **Role ARN**: (Optional) The ARN of an AWS IAM role to assume for authentication, serving as an alternative to using AWS API keys. - **SNS topic ARN**: (Optional) If you don't specify this value, you must specify a value for the `Phone number` or `Target ARN`. If you are using a FIFO SNS topic you should set a message group interval longer than 5 minutes to prevent messages with the same group key being deduplicated by the SNS default deduplication window. - **Phone number**: (Optional) Phone number if message is delivered via SMS in E.164 format. If you don't specify this value, you must specify a value for the `SNS topic ARN` or `Target ARN`. - **Target ARN**: (Optional) The mobile platform endpoint ARN if message is delivered via mobile notifications. If you don't specify this value, you must specify a value for the `SNS topic ARN` or `Phone number`. -- **Subject**: (Optional) Customize the subject line or use the default template. This field is templateable. -- **Message**: (Optional) Customize the message content or use the default template. This field is templateable. +- **Subject**: (Optional) Customize the subject. This field supports [notification templates](ref:notification-templates) and, by default, uses the default title template (`default.title`). It **cannot be an empty string**. +- **Message**: (Optional) Customize the message. This field supports [notification templates](ref:notification-templates) and, by default, uses the default message template (`default.message`). - **Attributes**: (Optional) Add any SNS message attributes. -{{< admonition type="note" >}} -Both `Access Key` and `Secret Key` must be provided together or left blank together. If blank it defaults to a chain of credential -providers to search for credentials in environment variables, shared credential file, and EC2 Instance Roles. +## Example using an Access Key -Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. -{{< /admonition >}} +This section outlines a minimal setup to configure Amazon SNS with Alerting. -### 3. Test & Save the Contact Point - -- Click **"Test"** to verify that the SNS configuration is working correctly. -- After the test is successful, click **"Save contact point"** to add the SNS contact point. - -### 4. Next steps - -The SNS contact point is ready to receive alert notifications. - -To add this contact point to your alert, complete the following steps. - -1. In Grafana, navigate to **Alerting** > **Alert rules**. -1. Edit or create a new alert rule. -1. Scroll down to the **Configure labels and notifications** section. -1. Under Notifications click **Select contact point**. -1. From the drop-down menu, select the previously created contact point. -1. **Click Save rule and exit**. - -## Example Minimal Setup Using Assumed IAM Role - -This section outlines a minimal setup to configure SNS with Grafana using an assumed IAM Role. - -### 1. Create an SNS Topic +### 1. Create an SNS Topic and Email Subscriber 1. **Navigate to SNS in AWS Console**: - Go to the [Amazon SNS Console](https://console.aws.amazon.com/sns/v3/home). -2. **Create a new topic** [[AWS Documentation](https://docs.aws.amazon.com/sns/latest/dg/sns-create-topic.html)]: +2. **Create a new topic**: - On the **Topics** page, choose **"Create topic"**. - Select **"Standard"** as the type. @@ -99,105 +100,70 @@ This section outlines a minimal setup to configure SNS with Grafana using an ass - **Encryption**: Leave disabled for this minimal setup. - Click **"Create topic"**. -3. (Optional) **Add an email subscriber to help test** [[AWS Documentation](https://docs.aws.amazon.com/sns/latest/dg/sns-email-notifications.html)]: +3. (Optional) **Add an email subscriber to help test**: - Within your newly created topic, click on **"Create subscription"**. - **Protocol**: Choose `Email`. - **Endpoint**: Enter your email address to receive test notifications. - Click **"Create subscription"**. - **Confirm Subscription**: Check your email and confirm the subscription by clicking the provided link. -### 2. Create an IAM Role +### 2. Create an IAM Policy, User, and Access Key 1. **Navigate to IAM in AWS Console**: - Go to the [IAM Console](https://console.aws.amazon.com/iam/home). -2. **Create a new role** [[AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html)]: +2. **Create a new policy**: - - On the **Roles** page, choose **"Create role"**. - - **Trusted Entity**: Select **"This account"**. - - Click **"Next"** until the end, name it (e.g., `GrafanaSNSRole`), and click **"Create role"**. - -3. **Attach Inline Policy**: - - - After creating the role, select it and navigate to the **"Permissions"** tab. - - Click on **"Add permission"** > **"Create inline policy"**. + - On the **Policies** page, choose **"Create policy"**. - Switch to the **"JSON"** tab and paste the following policy, replacing `Resource` with your SNS topic ARN: - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["sns:Publish", "sns:GetTopicAttributes"], - "Resource": "arn:aws:sns:::" - } - ] - } - ``` + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["sns:Publish", "sns:GetTopicAttributes"], + "Resource": "arn:aws:sns:::" + } + ] + } + ``` - Click **"Next"**, name it (e.g., `SNSPublishPolicy`), and click **"Create policy"**. -### 3. Create an IAM Policy - -1. **Create a new policy to allow assuming the above IAM role** [[AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create-console.html)]: - - - In the IAM Console, on the **Policies** page, choose **"Create policy"**. - - Switch to the **"JSON"** tab and paste the following policy, replacing `Resource` with the ARN of the role you created earlier: - - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "sts:AssumeRole", - "Resource": "arn:aws:iam:::role/GrafanaSNSRole" - } - ] - } - ``` - -2. **Review and Create**: - - Click **"Next"**, name it (e.g., `AssumeSNSRolePolicy`), and click **"Create policy"**. - -### 4. Create an IAM User - -1. **Create a new IAM user to assume the above role** [[AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html)]: +3. **Create a new IAM user and assign the policy** - In the IAM Console, on the **Users** page, choose **"Create user"**. - - Enter a **User name**, e.g., `grafana-sns-user`. + - Enter a **User name**, e.g., `alerting-sns-user`. - Click **"Next"**. - - Select **"Attach policies directly"**. - - Search for the policy you created earlier (`AssumeSNSRolePolicy`) and select it. + - In **Set permissions**, select **"Attach policies directly"**. + - Search for the policy you created earlier (`SNSPublishPolicy`) and select it. - Click **"Next"** , and click **"Create user"**. -2. **Generate credentials**: +4. **Create an Access Key**: - Within your newly created user, click on **"Create access key"**. - Select an appropriate use-case, e.g., `Application running outside AWS`. - Click **"Next"** , and click **"Create access key"**. - - **Save Credentials**: Note the **Access key ID** and **Secret access key**. You'll need these for Grafana's configuration. + - **Save Credentials**: Note the **Access key ID** and **Secret access key** that are required in the next step. -### 5. Add the SNS Contact Point in Grafana +### 3. Configure the SNS Contact Point in Grafana -After creating the IAM user and obtaining the necessary credentials, proceed to [configure the SNS contact point in Grafana]({{< relref "#adding-the-sns-contact-point-in-grafana" >}}) using the following details: +Follow the steps in [configure Amazon SNS for a contact point](#configure-amazon-sns-for-a-contact-point), using the settings below and replacing the placeholders with the SNS and IAM values created in the previous steps. -- **The Amazon SNS API URL**: `https://sns.us-east-1.amazonaws.com` +- **The Amazon SNS API URL**: `https://sns..amazonaws.com` - **Signature Version (sigv4)**: - - **Region**: `us-east-1` + - **Region**: `` - **Access Key**: ``. - **Secret Key**: `` - - **Role ARN**: `arn:aws:iam:::role/GrafanaSNSRole` -- **SNS topic ARN**: `arn:aws:sns:::My-Topic` +- **SNS topic ARN**: `arn:aws:sns:::` -{{< admonition type="note" >}} -Replace the placeholder values (`https://sns.us-east-1.amazonaws.com`, `us-east-1`, ``, ``, `arn:aws:iam:::role/GrafanaSNSRole`, `arn:aws:sns:::My-Topic`) with your actual AWS credentials and ARNs. -{{< /admonition >}} +[Test the contact point](ref:test-contact-point) to ensure it's working, or [enable notifications](ref:enable-contact-point-notifications) for it. ## Additional Resources +- [Configure contact points](ref:configure-contact-points) - [Amazon SNS Documentation](https://docs.aws.amazon.com/sns/index.html) -- [AWS IAM Documentation](https://docs.aws.amazon.com/iam/index.html) -- [Prometheus Alertmanager SNS Integration](https://prometheus.io/docs/alerting/configuration/#sns_config) -- [Cloudwatch AWS Authentication]({{< relref "../../../../datasources/aws-cloudwatch/aws-authentication" >}}) +- [Amazon IAM Documentation](https://docs.aws.amazon.com/iam/index.html) +- [Prometheus Alertmanager SNS Configuration](https://prometheus.io/docs/alerting/configuration/#sns_config) From 9a0e874e9baf3b58e6aaa4b6bab356873691d6a4 Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Tue, 18 Feb 2025 17:34:04 -0500 Subject: [PATCH 13/34] Unified Search: remove date sort fields (#100924) --- public/app/features/search/service/unified.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts index 8e270aba37b..c1755c10517 100644 --- a/public/app/features/search/service/unified.ts +++ b/public/app/features/search/service/unified.ts @@ -101,10 +101,6 @@ export class UnifiedSearcher implements GrafanaSearcher { opts.push({ value: `-${sf.name}`, label: `${sf.display} (most)` }); opts.push({ value: `${sf.name}`, label: `${sf.display} (least)` }); } - for (const sf of sortTimeFields) { - opts.push({ value: `-${sf.name}`, label: `${sf.display} (recent)` }); - opts.push({ value: `${sf.name}`, label: `${sf.display} (oldest)` }); - } } return Promise.resolve(opts); @@ -283,12 +279,6 @@ const sortFields = [ { name: 'errors_last_30_days', display: 'Errors 30 days' }, ]; -// Enterprise only time sort field values for dashboards -const sortTimeFields = [ - { name: 'created_at', display: 'Created time' }, - { name: 'updated_at', display: 'Updated time' }, -]; - function noDataResponse(): QueryResponse | PromiseLike { return { view: new DataFrameView({ length: 0, fields: [] }), @@ -309,11 +299,6 @@ function getSortFieldDisplayName(name: string) { return sf.display; } } - for (const sf of sortTimeFields) { - if (sf.name === name) { - return sf.display; - } - } return name; } From 3e6f40c87386984be60e391b26a7559e3469dac3 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 18 Feb 2025 16:11:26 -0700 Subject: [PATCH 14/34] K8s: Move to one feature toggle for folders and dashboards (#100911) --- .../feature-toggles/index.md | 3 +- .../src/types/featureToggles.gen.ts | 3 +- pkg/api/dashboard.go | 2 +- pkg/api/folder_test.go | 2 +- pkg/registry/apis/folders/register.go | 2 +- .../accesscontrol/ossaccesscontrol/folder.go | 2 +- .../dashboards/service/dashboard_service.go | 46 +++++----- .../service/dashboard_service_test.go | 13 ++- .../dashboardversion/dashverimpl/dashver.go | 4 +- .../dashverimpl/dashver_test.go | 4 +- pkg/services/featuremgmt/registry.go | 12 +-- pkg/services/featuremgmt/toggles-gitlog.csv | 4 +- pkg/services/featuremgmt/toggles_gen.csv | 3 +- pkg/services/featuremgmt/toggles_gen.go | 10 +- pkg/services/featuremgmt/toggles_gen.json | 39 ++------ pkg/services/folder/folderimpl/folder.go | 24 ++--- .../folderimpl/folder_unifiedstorage.go | 91 +++++++------------ .../folderimpl/folder_unifiedstorage_test.go | 82 +++++++---------- pkg/services/stats/statsimpl/stats.go | 2 +- pkg/storage/unified/README.md | 9 +- pkg/tests/apis/folder/folders_test.go | 22 ++--- pkg/tests/apis/openapi_test.go | 4 +- .../apis/provisioning/provisioning_test.go | 3 +- .../dashboard-scene/scene/DashboardScene.tsx | 2 +- .../settings/VersionsEditView.tsx | 2 +- .../DashboardSettings/VersionsSettings.tsx | 2 +- .../VersionHistory/useDashboardRestore.tsx | 3 +- 27 files changed, 162 insertions(+), 233 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index dbb5042c2cd..9e4e04d87a4 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -168,9 +168,8 @@ Experimental features might be changed or removed without prior notice. | `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) | | `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint | | `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards | -| `kubernetesCliDashboards` | Use the k8s client to retrieve dashboards internally | | `kubernetesRestore` | Allow restoring objects in k8s | -| `kubernetesFoldersServiceV2` | Use the Folders Service V2, and route Folder Service requests to k8s | +| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | | `datasourceQueryTypes` | Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) | | `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query | | `queryServiceRewrite` | Rewrite requests targeting /ds/query to the query service | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index a9283179430..5460601f8d4 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -108,9 +108,8 @@ export interface FeatureToggles { kubernetesPlaylists?: boolean; kubernetesSnapshots?: boolean; kubernetesDashboards?: boolean; - kubernetesCliDashboards?: boolean; kubernetesRestore?: boolean; - kubernetesFoldersServiceV2?: boolean; + kubernetesClientDashboardsFolders?: boolean; datasourceQueryTypes?: boolean; queryService?: boolean; queryServiceRewrite?: boolean; diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 013dbebb5eb..e74b689601a 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -192,7 +192,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response } metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetDashboard).Inc() // lookup folder title & url - if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { queryResult, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{ OrgID: c.SignedInUser.GetOrgID(), UID: &dash.FolderUID, diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index a464727a31b..97701e0f4f0 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -625,7 +625,7 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) { featuresArr := []any{featuremgmt.FlagNestedFolders} if tc.unifiedStorageEnabled { - featuresArr = append(featuresArr, featuremgmt.FlagKubernetesFoldersServiceV2) + featuresArr = append(featuresArr, featuremgmt.FlagKubernetesClientDashboardsFolders) } server := SetupAPITestServer(t, func(hs *HTTPServer) { diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 41beb600817..1ca84a49a4f 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -66,7 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg, unified resource.ResourceClient, ) *FolderAPIBuilder { if !featuremgmt.AnyEnabled(features, - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagProvisioning) { return nil // skip registration unless opting into Kubernetes folders or unless we want to customize registration when testing diff --git a/pkg/services/accesscontrol/ossaccesscontrol/folder.go b/pkg/services/accesscontrol/ossaccesscontrol/folder.go index 8d1dc7b0d59..ee41326c878 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/folder.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/folder.go @@ -110,7 +110,7 @@ func ProvideFolderPermissions( // if the folder is not found, this may be on the create path, // where the write path to legacy will then go through the read // path and try to read from both legacy & unified before it exists on both - if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && errors.Is(err, dashboards.ErrFolderNotFound) { + if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && errors.Is(err, dashboards.ErrFolderNotFound) { _, err = folderService.GetLegacy(ctx, &folder.GetFolderQuery{ UID: &resourceID, OrgID: orgID, diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 552e9805e52..893f944e7dd 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -152,7 +152,7 @@ func (dr *DashboardServiceImpl) getPermissionsService(isFolder bool) accesscontr } func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { u := "a.Map{} orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{}) if err != nil { @@ -188,7 +188,7 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc } func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { resp, err := dr.k8sclient.GetStats(ctx, orgID) if err != nil { return 0, err @@ -226,7 +226,7 @@ func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { } func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, name string) ([]*dashboards.DashboardProvisioning, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{}) if err != nil { return nil, err @@ -267,7 +267,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, } func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { // if dashboard id is 0, it is a new dashboard if dashboardID == 0 { return nil, nil @@ -301,7 +301,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con } func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { if dashboardUID == "" { return nil, nil } @@ -368,7 +368,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d } // Validate folder - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{ OrgID: dash.OrgID, UID: &dash.FolderUID, @@ -555,7 +555,7 @@ func (dr *DashboardServiceImpl) ValidateDashboardBeforeSave(ctx context.Context, } func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { // check each org for orphaned provisioned dashboards orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{}) if err != nil { @@ -673,7 +673,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt } var dash *dashboards.Dashboard - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { // save the dashboard but then do NOT return // we want to save the provisioning data to the dashboard_provisioning table still // to ensure we can safely rollback to mode2 if needed @@ -748,7 +748,7 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar } func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboards.SaveDashboardCommand) (*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return dr.saveDashboardThroughK8s(ctx, cmd, cmd.OrgID) } @@ -756,7 +756,7 @@ func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboar } func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid, IncludeDeleted: true}) } @@ -815,7 +815,7 @@ func (dr *DashboardServiceImpl) SoftDeleteDashboard(ctx context.Context, orgID i return fmt.Errorf("feature flag %s is not enabled", featuremgmt.FlagDashboardRestore) } - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { // deletes in unistore are soft deletes, so we can just delete in the same way return dr.deleteDashboardThroughK8s(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgID, UID: dashboardUID}, true) } @@ -836,7 +836,7 @@ func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId // DeleteAllDashboards will delete all dashboards within a given org. func (dr *DashboardServiceImpl) DeleteAllDashboards(ctx context.Context, orgId int64) error { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return dr.deleteAllDashboardThroughK8s(ctx, orgId) } @@ -859,7 +859,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID} - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { err := dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard) if err != nil { return err @@ -924,7 +924,7 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo // UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed // and provisioned dashboards are left behind but not deleted. func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{}) if err != nil { return err @@ -957,7 +957,7 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo } func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, query *dashboards.GetDashboardsByPluginIDQuery) ([]*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ OrgId: query.OrgID, ProvisionedRepo: dashboard.PluginIDRepoName, @@ -1055,7 +1055,7 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, } func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return dr.getDashboardThroughK8s(ctx, query) } @@ -1063,7 +1063,7 @@ func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboa } func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { requester, err := identity.GetRequester(ctx) if err != nil { return nil, err @@ -1089,7 +1089,7 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query * } func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { if query.OrgID == 0 { requester, err := identity.GetRequester(ctx) if err != nil { @@ -1237,7 +1237,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb }(time.Now()) } - if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) { if query.OrgId == 0 { requester, err := identity.GetRequester(ctx) if err != nil { @@ -1319,7 +1319,7 @@ func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *das } func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { requester, err := identity.GetRequester(ctx) if err != nil { return nil, err @@ -1331,7 +1331,7 @@ func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashbo } func (dr *DashboardServiceImpl) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return dr.listDashboardsThroughK8s(ctx, orgID) } @@ -1405,7 +1405,7 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb } func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) { - if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) { res, err := dr.k8sclient.Search(ctx, query.OrgID, &resource.ResourceSearchRequest{ Facet: map[string]*resource.ResourceSearchRequest_Facet{ "tags": { @@ -1437,7 +1437,7 @@ func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *das } func (dr DashboardServiceImpl) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ OrgId: orgID, FolderUIDs: folderUIDs, diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 68ebc0ac61a..32656bf0d19 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -265,7 +265,7 @@ func TestDashboardService(t *testing.T) { func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *client.MockK8sHandler) { mockCli := new(client.MockK8sHandler) service.k8sclient = mockCli - service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards) + service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) ctx := context.Background() userCtx := &user.SignedInUser{UserID: 1, OrgID: 1} @@ -1096,7 +1096,13 @@ func TestSaveProvisionedDashboard(t *testing.T) { service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, - log: log.NewNopLogger(), + folderService: &foldertest.FakeService{ + ExpectedFolder: &folder.Folder{ + ID: 0, + UID: "general", + }, + }, + log: log.NewNopLogger(), } origNewDashboardGuardian := guardian.New @@ -1160,6 +1166,9 @@ func TestSaveDashboard(t *testing.T) { service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, + folderService: &foldertest.FakeService{ + ExpectedFolder: &folder.Folder{}, + }, } origNewDashboardGuardian := guardian.New diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index 475e57413be..26e38cf8def 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -86,7 +86,7 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue query.DashboardID = id } - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { version, err := s.getHistoryThroughK8s(ctx, query.OrgID, query.DashboardUID, query.Version) if err != nil { return nil, err @@ -157,7 +157,7 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions query.Limit = 1000 } - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { versions, err := s.listHistoryThroughK8s( ctx, query.OrgID, diff --git a/pkg/services/dashboardversion/dashverimpl/dashver_test.go b/pkg/services/dashboardversion/dashverimpl/dashver_test.go index 5d71b3457d1..df074594e9e 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver_test.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver_test.go @@ -44,7 +44,7 @@ func TestDashboardVersionService(t *testing.T) { dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()} mockCli := new(client.MockK8sHandler) dashboardVersionService.k8sclient = mockCli - dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards) + dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil) mockCli.On("GetUserFromMeta", mock.Anything, "user:1").Return(&user.User{ID: 1}, nil) @@ -232,7 +232,7 @@ func TestListDashboardVersions(t *testing.T) { dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()} mockCli := new(client.MockK8sHandler) dashboardVersionService.k8sclient = mockCli - dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards) + dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")). diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index c877b2c6c2e..3f4bedc4636 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -680,12 +680,6 @@ var ( Owner: grafanaAppPlatformSquad, FrontendOnly: true, }, - { - Name: "kubernetesCliDashboards", - Description: "Use the k8s client to retrieve dashboards internally", - Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, - }, { Name: "kubernetesRestore", Description: "Allow restoring objects in k8s", @@ -693,10 +687,10 @@ var ( Owner: grafanaAppPlatformSquad, }, { - Name: "kubernetesFoldersServiceV2", - Description: "Use the Folders Service V2, and route Folder Service requests to k8s", + Name: "kubernetesClientDashboardsFolders", + Description: "Route the folder and dashboard service requests to k8s", Stage: FeatureStageExperimental, - Owner: grafanaSearchAndStorageSquad, + Owner: grafanaAppPlatformSquad, }, { Name: "datasourceQueryTypes", diff --git a/pkg/services/featuremgmt/toggles-gitlog.csv b/pkg/services/featuremgmt/toggles-gitlog.csv index c132b9adfe2..7aa2c793d10 100644 --- a/pkg/services/featuremgmt/toggles-gitlog.csv +++ b/pkg/services/featuremgmt/toggles-gitlog.csv @@ -360,7 +360,6 @@ appPlatformAccessTokens,2024-09-05T16:18:44Z,2024-10-14T10:47:18Z,d5ebaa0ef92ede appSidecar,2024-09-09T12:45:05Z,,5e2ac24890906e5070323d87730dd78a4f885963,Andrej Ocenas vizActions,2024-09-09T14:11:55Z,,af48d3db1eb2d8681843f5997e50fea5e5ea3096,Adela Almasan groupAttributeSync,2024-09-09T15:29:43Z,,6ded6a8872204a818b3795dc733cc5fe5db066a0,Aaron Godin -kubernetesFolders,2024-09-10T09:22:08Z,,b12a29a1dac8b9aec4a99be08e1665939cb27dc5,Arati R. alertingFilterV2,2024-09-11T11:29:26Z,,90ee52e8d9c14237f8a57b622c0def7512e657cd,Gilles De Mey improvedExternalSessionHandling,2024-09-17T10:54:39Z,,41cd0f51800d4849345fc0980ca4173967fc8e9e,Misi datasourceAPIServers,2024-09-19T08:28:27Z,,f21a5987a22bcdb596d6a258d2960e4151348b63,Ryan McKinley @@ -407,7 +406,6 @@ feedbackButton,2024-12-02T17:08:15Z,,8a1b89a5ebb847f6b29e92dcea796f00c30431d6,Mi elasticsearchCrossClusterSearch,2024-12-12T22:20:04Z,,b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f,Isabella Siu unifiedHistory,2024-12-13T10:41:18Z,,aac62c89dae1092836a91d1b6ae6bd7127fe676a,Laura Fernández lokiLabelNamesQueryApi,2024-12-13T14:31:41Z,,5ac7443fcec0db412d3333044a82c2c26b5aece7,Sven Grossmann -kubernetesCliDashboards,2024-12-13T22:55:43Z,,8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4,Stephanie Hingtgen useV2DashboardsAPI,2024-12-17T21:17:09Z,,070f0e4457c5967102ef157197073dc2662f6fb8,Dominik Prokop investigationsBackend,2024-12-18T08:31:03Z,,f46c07aba7b6faccd2ecafc83051d1410cacc867,Jackson Coelho unifiedStorageSearchSprinkles,2024-12-18T17:00:54Z,,4837585cab0fd84184a8c6f5d6891f442a2b95f1,owensmallwood @@ -421,9 +419,9 @@ improvedExternalSessionHandlingSAML,2025-01-09T17:02:49Z,,c52ec21c75ab72c2f7d282 teamHttpHeadersMimir,2025-01-13T10:42:47Z,,04acbcdef23f673bd6bbfdbbece29c9769ce155a,Eric Leijonmarck ABTestFeatureToggleA,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs ABTestFeatureToggleB,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs -kubernetesFoldersServiceV2,2025-01-13T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,maicon queryLibraryDashboards,2025-01-14T11:01:15Z,,740cd22fe51a3543c182857f31fc97fd42263306,Ashley Harrison elasticsearchImprovedParsing,2025-01-15T17:05:54Z,,bab55a4cb84f2ba57838f96a492ab9aa7f307957,Adam Yeats grafanaAdvisor,2025-01-20T10:08:00Z,,c1364d6be6f552203ba786f17a89664304b89247,Andres Martinez Gotor datasourceConnectionsTab,2025-01-21T17:39:48Z,,97d8f68b705f9949493079d1833abfe80e7b48f3,Syerikjan Kh unifiedStorageSearchPermissionFiltering,2025-01-22T11:38:37Z,,dd483fc17fa4a2931848e3574cfc31ea6f6530d9,owensmallwood +kubernetesClientDashboardsFolders,2025-02-18T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,Stephanie Hingtgen diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 57172123e7a..88f68f862f5 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -89,9 +89,8 @@ formatString,GA,@grafana/dataviz-squad,false,false,true kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true -kubernetesCliDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,false kubernetesRestore,experimental,@grafana/grafana-app-platform-squad,false,false,false -kubernetesFoldersServiceV2,experimental,@grafana/search-and-storage,false,false,false +kubernetesClientDashboardsFolders,experimental,@grafana/grafana-app-platform-squad,false,false,false datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index c21efcbae3a..f8ebdbe95c3 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -367,17 +367,13 @@ const ( // Use the kubernetes API in the frontend for dashboards FlagKubernetesDashboards = "kubernetesDashboards" - // FlagKubernetesCliDashboards - // Use the k8s client to retrieve dashboards internally - FlagKubernetesCliDashboards = "kubernetesCliDashboards" - // FlagKubernetesRestore // Allow restoring objects in k8s FlagKubernetesRestore = "kubernetesRestore" - // FlagKubernetesFoldersServiceV2 - // Use the Folders Service V2, and route Folder Service requests to k8s - FlagKubernetesFoldersServiceV2 = "kubernetesFoldersServiceV2" + // FlagKubernetesClientDashboardsFolders + // Route the folder and dashboard service requests to k8s + FlagKubernetesClientDashboardsFolders = "kubernetesClientDashboardsFolders" // FlagDatasourceQueryTypes // Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index a3bfd79d3a6..d64d30918e5 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2227,12 +2227,15 @@ }, { "metadata": { - "name": "kubernetesCliDashboards", - "resourceVersion": "1733520389522", - "creationTimestamp": "2024-12-13T22:55:43Z" + "name": "kubernetesClientDashboardsFolders", + "resourceVersion": "1739913583714", + "creationTimestamp": "2025-02-18T21:15:35Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-02-18 21:19:43.714638 +0000 UTC" + } }, "spec": { - "description": "Use the k8s client to retrieve dashboards internally", + "description": "Route the folder and dashboard service requests to k8s", "stage": "experimental", "codeowner": "@grafana/grafana-app-platform-squad" } @@ -2264,34 +2267,6 @@ "hideFromAdminPage": true } }, - { - "metadata": { - "name": "kubernetesFolders", - "resourceVersion": "1725863636605", - "creationTimestamp": "2024-09-10T09:22:08Z", - "deletionTimestamp": "2025-01-22T20:49:15Z", - "annotations": { - "grafana.app/updatedTimestamp": "2024-09-09 06:33:56.605329 +0000 UTC" - } - }, - "spec": { - "description": "Use the kubernetes API in the frontend for folders, and route /api/folders requests to k8s", - "stage": "experimental", - "codeowner": "@grafana/search-and-storage" - } - }, - { - "metadata": { - "name": "kubernetesFoldersServiceV2", - "resourceVersion": "1735336477446", - "creationTimestamp": "2025-01-13T21:15:35Z" - }, - "spec": { - "description": "Use the Folders Service V2, and route Folder Service requests to k8s", - "stage": "experimental", - "codeowner": "@grafana/search-and-storage" - } - }, { "metadata": { "name": "kubernetesPlaylists", diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 119d84bc940..df2c12ab3a5 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -111,7 +111,7 @@ func ProvideService( ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv)) - if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { k8sHandler := client.NewK8sHandler( cfg, request.GetNamespaceMapper(cfg), @@ -129,7 +129,7 @@ func ProvideService( srv.k8sclient = k8sHandler } - if features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { dashHandler := client.NewK8sHandler( cfg, request.GetNamespaceMapper(cfg), @@ -193,7 +193,7 @@ func (s *Service) DBMigration(db db.DB) { } func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery) (model.HitList, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { // TODO: // - implement filtering by alerting folders and k6 folders (see the dashboards store `FindDashboards` method for reference) // - implement fallback on search client in unistore to go to legacy store (will need to read from dashboard store) @@ -204,7 +204,7 @@ func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery } func (s *Service) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.getFoldersFromApiServer(ctx, q) } return s.GetFoldersLegacy(ctx, q) @@ -263,7 +263,7 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery } func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.getFromApiServer(ctx, q) } return s.GetLegacy(ctx, q) @@ -404,7 +404,7 @@ func (s *Service) setFullpath(ctx context.Context, f *folder.Folder, user identi } func (s *Service) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.getChildrenFromApiServer(ctx, q) } return s.GetChildrenLegacy(ctx, q) @@ -674,7 +674,7 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo } func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.getParentsFromApiServer(ctx, q) } return s.GetParentsLegacy(ctx, q) @@ -707,7 +707,7 @@ func (s *Service) getFolderByTitle(ctx context.Context, orgID int64, title strin } func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.createOnApiServer(ctx, cmd) } return s.CreateLegacy(ctx, cmd) @@ -826,7 +826,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm } func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.updateOnApiServer(ctx, cmd) } return s.UpdateLegacy(ctx, cmd) @@ -965,7 +965,7 @@ func prepareForUpdate(dashFolder *dashboards.Dashboard, orgId int64, userId int6 } func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) error { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.deleteFromApiServer(ctx, cmd) } return s.DeleteLegacy(ctx, cmd) @@ -1088,7 +1088,7 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm } func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.moveOnApiServer(ctx, cmd) } return s.MoveLegacy(ctx, cmd) @@ -1315,7 +1315,7 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold } func (s *Service) GetDescendantCounts(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) { - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) { + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { return s.getDescendantCountsFromApiServer(ctx, q) } diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage.go b/pkg/services/folder/folderimpl/folder_unifiedstorage.go index 94a19aa17b5..283f119accd 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go @@ -26,7 +26,6 @@ import ( "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/search/model" - "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/search" @@ -659,71 +658,43 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol return folder.ErrFolderNotEmpty.Errorf("folder contains %d alert rules", alertRulesInFolder) } - // if dashboard restore is on we don't delete public dashboards, the hard delete will take care of it later - if !s.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) { - // We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards - var dashboardUIDs []string - // we cannot use the dashboard service directly due to circular dependencies, - // so either use the search client if the feature is enabled or use the dashboard store - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { - request := &resource.ResourceSearchRequest{ - Options: &resource.ListOptions{ - Labels: []*resource.Requirement{}, - Fields: []*resource.Requirement{ - { - Key: resource.SEARCH_FIELD_FOLDER, - Operator: string(selection.In), - Values: folders, - }, - }, + // We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards - + // we cannot use the dashboard service directly due to circular dependencies, so use the search client to get the dashboards + request := &resource.ResourceSearchRequest{ + Options: &resource.ListOptions{ + Labels: []*resource.Requirement{}, + Fields: []*resource.Requirement{ + { + Key: resource.SEARCH_FIELD_FOLDER, + Operator: string(selection.In), + Values: folders, }, - Limit: 100000} + }, + }, + Limit: 100000} - res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request) - if err != nil { - return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) - } + res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request) + if err != nil { + return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) + } - hits, err := dashboardsearch.ParseResults(res, 0) - if err != nil { - return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) - } - dashboardUIDs = make([]string, len(hits.Hits)) - for i, dashboard := range hits.Hits { - dashboardUIDs[i] = dashboard.Name - err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{}) - if err != nil { - return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err) - } - } - } else { - dashes, err := s.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: cmd.SignedInUser, - FolderUIDs: folders, - OrgId: cmd.OrgID, - Type: searchstore.TypeDashboard, - }) - if err != nil { - return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) - } - dashboardUIDs = make([]string, len(dashes)) - for i, dashboard := range dashes { - dashboardUIDs[i] = dashboard.UID - err = s.dashboardStore.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{ - UID: dashboard.UID, - OrgID: cmd.OrgID, - }) - if err != nil { - return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err) - } - } - } - // Delete all public dashboards in the folders - err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs) + hits, err := dashboardsearch.ParseResults(res, 0) + if err != nil { + return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) + } + dashboardUIDs := make([]string, len(hits.Hits)) + for i, dashboard := range hits.Hits { + dashboardUIDs[i] = dashboard.Name + err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{}) if err != nil { - return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err) + return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err) } } + // Delete all public dashboards in the folders + err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs) + if err != nil { + return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err) + } } err = s.unifiedStore.Delete(ctx, folders, cmd.OrgID) diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go index 5711f4aa713..a5667a5f4fc 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go @@ -179,7 +179,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { } featuresArr := []any{ - featuremgmt.FlagKubernetesFoldersServiceV2} + featuremgmt.FlagKubernetesClientDashboardsFolders} features := featuremgmt.WithFeatures(featuresArr...) dashboardStore := dashboards.NewFakeDashboardStore(t) @@ -206,6 +206,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { publicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) + fakeK8sClient := new(client.MockK8sHandler) folderService := &Service{ log: slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"), unifiedStore: unifiedStore, @@ -216,7 +217,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { metrics: newFoldersMetrics(nil), tracer: tracing.InitializeTracerForTest(), k8sclient: k8sCli, - dashboardStore: dashboardStore, + dashboardK8sClient: fakeK8sClient, publicDashboardService: publicDashboardService, } @@ -348,7 +349,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { }) t.Run("When deleting folder by uid should not return access denied error - ForceDeleteRules false", func(t *testing.T) { - dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once() + fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once() publicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) err := folderService.Delete(ctx, &folder.DeleteFolderCommand{ @@ -362,6 +363,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { t.Run("When deleting folder by uid, expectedForceDeleteRules as false, and dashboard Restore turned on should not return access denied error", func(t *testing.T) { folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...) + fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once() expectedForceDeleteRules := false err := folderService.Delete(ctx, &folder.DeleteFolderCommand{ @@ -375,6 +377,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { t.Run("When deleting folder by uid, expectedForceDeleteRules as true, and dashboard Restore turned on should not return access denied error", func(t *testing.T) { folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...) + fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once() expectedForceDeleteRules := true err := folderService.Delete(ctx, &folder.DeleteFolderCommand{ @@ -526,7 +529,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) { } service := Service{ k8sclient: fakeK8sClient, - features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2), + features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders), unifiedStore: folderStore, } user := &user.SignedInUser{OrgID: 1} @@ -779,7 +782,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) { dashboardStore: dashboardStore, publicDashboardService: publicDashboardFakeService, registry: make(map[string]folder.RegistryService), - features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2), + features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders), } user := &user.SignedInUser{OrgID: 1} ctx := identity.WithRequester(context.Background(), user) @@ -798,52 +801,22 @@ func TestDeleteFoldersFromApiServer(t *testing.T) { require.NoError(t, service.RegisterService(alertingStore)) t.Run("Should delete folder", func(t *testing.T) { - dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once() - publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{}).Return(nil).Once() + dashboardK8sclient.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once() err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{ - UID: "uid", + UID: "uid1", OrgID: 1, SignedInUser: user, }) require.NoError(t, err) - }) - - t.Run("Should delete dashboards and public dashboards within the folder", func(t *testing.T) { - dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{ - { - UID: "test", - OrgID: 1, - }, - { - UID: "test2", - OrgID: 1, - }, - }, nil).Once() - dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{ - UID: "test", - OrgID: 1, - }).Return(nil).Once() - dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{ - UID: "test2", - OrgID: 1, - }).Return(nil).Once() - publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once() - err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{ - UID: "uid", - OrgID: 1, - SignedInUser: user, - }) - require.NoError(t, err) - dashboardStore.AssertExpectations(t) + dashboardK8sclient.AssertExpectations(t) publicDashboardFakeService.AssertExpectations(t) }) - // enable k8s ff for dashboards, retest - service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2, featuremgmt.FlagKubernetesCliDashboards) - - t.Run("Should delete dashboards and public dashboards within the folder through k8s if the ff is enabled", func(t *testing.T) { - publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"uid1"}).Return(nil).Once() - dashboardK8sclient.On("Delete", mock.Anything, "uid1", int64(1), mock.Anything).Return(nil).Once() + t.Run("Should delete folders, dashboards, and public dashboards within the folder", func(t *testing.T) { + fakeFolderStore.ExpectedFolders = []*folder.Folder{{UID: "uid2", ID: 2}} + dashboardK8sclient.On("Delete", mock.Anything, "test", int64(1), mock.Anything).Return(nil).Once() + dashboardK8sclient.On("Delete", mock.Anything, "test2", int64(1), mock.Anything).Return(nil).Once() dashboardK8sclient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{ Options: &resource.ListOptions{ Labels: []*resource.Requirement{}, @@ -851,7 +824,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) { { Key: resource.SEARCH_FIELD_FOLDER, Operator: string(selection.In), - Values: []string{"uid1"}, + Values: []string{"uid", "uid2"}, }, }, }, @@ -870,11 +843,21 @@ func TestDeleteFoldersFromApiServer(t *testing.T) { Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ - Name: "uid1", - Resource: "folder", + Name: "test", + Resource: "dashboard", }, Cells: [][]byte{ - []byte("folder1"), + []byte("uid"), + []byte(""), + }, + }, + { + Key: &resource.ResourceKey{ + Name: "test2", + Resource: "dashboard", + }, + Cells: [][]byte{ + []byte("uid2"), []byte(""), }, }, @@ -882,13 +865,14 @@ func TestDeleteFoldersFromApiServer(t *testing.T) { }, TotalHits: 1, }, nil).Once() + publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once() err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{ - UID: "uid1", + UID: "uid", OrgID: 1, SignedInUser: user, }) require.NoError(t, err) + dashboardStore.AssertExpectations(t) publicDashboardFakeService.AssertExpectations(t) - dashboardK8sclient.AssertExpectations(t) }) } diff --git a/pkg/services/stats/statsimpl/stats.go b/pkg/services/stats/statsimpl/stats.go index ab6847d0b61..e35a89d27ed 100644 --- a/pkg/services/stats/statsimpl/stats.go +++ b/pkg/services/stats/statsimpl/stats.go @@ -165,7 +165,7 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS sb.Write(`(SELECT COUNT(DISTINCT (` + dialect.Quote("rule_group") + `)) FROM ` + dialect.Quote("alert_rule") + `) AS rule_groups,`) } // currently not supported when dashboards are in unified storage - if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { sb.Write(`(SELECT SUM(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_total,`, dialect.BooleanStr(false)) sb.Write(`(SELECT MAX(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_max,`, dialect.BooleanStr(false)) } diff --git a/pkg/storage/unified/README.md b/pkg/storage/unified/README.md index 6a34005f24b..d19d679b77c 100644 --- a/pkg/storage/unified/README.md +++ b/pkg/storage/unified/README.md @@ -61,9 +61,14 @@ target = all protocol = https [feature_toggles] -; store folders in k8s -kubernetesFolders = true grafanaAPIServerWithExperimentalAPIs = true +kubernetesClientDashboardsFolders = true + +[unified_storage.folders.folder.grafana.app] +dualWriterMode = 4 + +[unified_storage.dashboards.dashboard.grafana.app] +dualWriterMode = 4 [grafana-apiserver] ; use unified storage for k8s apiserver diff --git a/pkg/tests/apis/folder/folders_test.go b/pkg/tests/apis/folder/folders_test.go index 4b30d9428cf..2b317103876 100644 --- a/pkg/tests/apis/folder/folders_test.go +++ b/pkg/tests/apis/folder/folders_test.go @@ -49,7 +49,7 @@ func TestIntegrationFoldersApp(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ AppModeProduction: true, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, }) @@ -123,7 +123,7 @@ func TestIntegrationFoldersApp(t *testing.T) { }, }, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, })) }) @@ -139,7 +139,7 @@ func TestIntegrationFoldersApp(t *testing.T) { }, }, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, })) }) @@ -155,7 +155,7 @@ func TestIntegrationFoldersApp(t *testing.T) { }, }, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagNestedFolders, }, })) @@ -172,7 +172,7 @@ func TestIntegrationFoldersApp(t *testing.T) { }, }, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagNestedFolders, }, })) @@ -189,7 +189,7 @@ func TestIntegrationFoldersApp(t *testing.T) { }, }, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagNestedFolders, }, })) @@ -206,7 +206,7 @@ func TestIntegrationFoldersApp(t *testing.T) { }, }, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagNestedFolders, }, })) @@ -577,7 +577,7 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) { }, EnableFeatureToggles: []string{ featuremgmt.FlagNestedFolders, - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, }) @@ -679,7 +679,7 @@ func TestIntegrationFolderGetPermissions(t *testing.T) { }, EnableFeatureToggles: []string{ featuremgmt.FlagNestedFolders, - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, }) @@ -856,7 +856,7 @@ func TestFoldersCreateAPIEndpointK8S(t *testing.T) { }, EnableFeatureToggles: []string{ featuremgmt.FlagNestedFolders, - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, }) @@ -1026,7 +1026,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) { }, EnableFeatureToggles: []string{ featuremgmt.FlagNestedFolders, - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, }) diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index 2d57a16cddf..28c17981efa 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -28,8 +28,8 @@ func TestIntegrationOpenAPIs(t *testing.T) { h := NewK8sTestHelper(t, testinfra.GrafanaOpts{ AppModeProduction: true, EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesFoldersServiceV2, // Will be default on by G12 - featuremgmt.FlagQueryService, // Query Library + featuremgmt.FlagKubernetesClientDashboardsFolders, // Will be default on by G12 + featuremgmt.FlagQueryService, // Query Library featuremgmt.FlagProvisioning, featuremgmt.FlagInvestigationsBackend, }, diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go index ae9910e9667..28916508f08 100644 --- a/pkg/tests/apis/provisioning/provisioning_test.go +++ b/pkg/tests/apis/provisioning/provisioning_test.go @@ -35,8 +35,7 @@ func TestIntegrationProvisioning(t *testing.T) { EnableFeatureToggles: []string{ featuremgmt.FlagProvisioning, featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, - featuremgmt.FlagKubernetesCliDashboards, - featuremgmt.FlagKubernetesFoldersServiceV2, + featuremgmt.FlagKubernetesClientDashboardsFolders, }, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": { diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 1be803a66ea..3048549bcb9 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -373,7 +373,7 @@ export class DashboardScene extends SceneObjectBase { public onRestore = async (version: DecoratedRevisionModel): Promise => { let versionRsp; - if (config.featureToggles.kubernetesCliDashboards) { + if (config.featureToggles.kubernetesClientDashboardsFolders) { // the id here is the resource version in k8s, use this instead to get the specific version versionRsp = await historySrv.restoreDashboard(version.uid, version.id); } else { diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx index 17256382d0b..96431929a4c 100644 --- a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx @@ -136,7 +136,7 @@ export class VersionsEditView extends SceneObjectBase imp return; } let lhs, rhs; - if (config.featureToggles.kubernetesCliDashboards) { + if (config.featureToggles.kubernetesClientDashboardsFolders) { // the id here is the resource version in k8s, use this instead to get the specific version lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.id); rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.id); diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx index 30ccbe39708..ac4a4e89de6 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx @@ -94,7 +94,7 @@ export class VersionsSettings extends PureComponent { }); let lhs, rhs; - if (config.featureToggles.kubernetesCliDashboards) { + if (config.featureToggles.kubernetesClientDashboardsFolders) { // the id here is the resource version in k8s, use this instead to get the specific version lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.id); rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.id); diff --git a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx index 91fd0964aca..fa192c5a7b8 100644 --- a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx +++ b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx @@ -19,7 +19,8 @@ const restoreDashboard = async (version: number, dashboard: DashboardModel) => { export const useDashboardRestore = (id: number, version: number) => { const dashboard = useSelector((state) => state.dashboard.getModel()); const [state, onRestoreDashboard] = useAsyncFn( - async () => await restoreDashboard(config.featureToggles.kubernetesCliDashboards ? id : version, dashboard!), + async () => + await restoreDashboard(config.featureToggles.kubernetesClientDashboardsFolders ? id : version, dashboard!), [] ); const notifyApp = useAppNotification(); From 75189de4f76bfb6a2cec47a13fedb561769e5f1a Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:19:01 -0600 Subject: [PATCH 15/34] CI: Post-release process branch / PR migration (#100930) * update output for token in release-comms * use token from needs * generate token in individual workflows * Set secrets * define secrest in referenced workflows * wrong secret lol * oops * grafana/grafana -> grafana/grafana-enterprise --- .../workflows/create-next-release-branch.yml | 16 ++++++++-- .github/workflows/migrate-prs.yml | 16 ++++++++-- .github/workflows/release-comms.yml | 32 ++++++++----------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/.github/workflows/create-next-release-branch.yml b/.github/workflows/create-next-release-branch.yml index e1cc71c02a0..8fc01cd442d 100644 --- a/.github/workflows/create-next-release-branch.yml +++ b/.github/workflows/create-next-release-branch.yml @@ -11,7 +11,9 @@ on: type: string required: true secrets: - token: + GRAFANA_DELIVERY_BOT_APP_ID: + required: true + GRAFANA_DELIVERY_BOT_APP_PEM: required: true outputs: branch: @@ -26,7 +28,9 @@ on: type: string required: true secrets: - token: + GRAFANA_DELIVERY_BOT_APP_ID: + required: true + GRAFANA_DELIVERY_BOT_APP_PEM: required: true jobs: main: @@ -34,10 +38,16 @@ jobs: outputs: branch: ${{ steps.branch.outputs.branch }} steps: + - name: "Generate token" + id: generate_token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Create release branch id: branch uses: grafana/grafana-github-actions-go/bump-release@main with: ownerRepo: ${{ inputs.ownerRepo }} source: ${{ inputs.source }} - token: ${{ secrets.token }} + token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/migrate-prs.yml b/.github/workflows/migrate-prs.yml index 9a41ee6ee8a..31bb8f9f9da 100644 --- a/.github/workflows/migrate-prs.yml +++ b/.github/workflows/migrate-prs.yml @@ -16,7 +16,9 @@ on: required: true type: string secrets: - token: + GRAFANA_DELIVERY_BOT_APP_ID: + required: true + GRAFANA_DELIVERY_BOT_APP_PEM: required: true workflow_dispatch: inputs: @@ -33,17 +35,25 @@ on: required: true type: string secrets: - token: + GRAFANA_DELIVERY_BOT_APP_ID: + required: true + GRAFANA_DELIVERY_BOT_APP_PEM: required: true jobs: main: runs-on: ubuntu-latest steps: + - name: "Generate token" + id: generate_token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Migrate PRs uses: grafana/grafana-github-actions-go/migrate-open-prs@main with: - token: ${{ secrets.token }} + token: ${{ steps.generate_token.outputs.token }} ownerRepo: ${{ inputs.ownerRepo }} from: ${{ inputs.from }} to: ${{ inputs.to }} diff --git a/.github/workflows/release-comms.yml b/.github/workflows/release-comms.yml index 86e698df758..720948e659b 100644 --- a/.github/workflows/release-comms.yml +++ b/.github/workflows/release-comms.yml @@ -30,15 +30,8 @@ jobs: release_branch: ${{ steps.output.outputs.release_branch }} dry_run: ${{ steps.output.outputs.dry_run }} latest: ${{ steps.output.outputs.latest }} - token: ${{ steps.output.outputs.token }} runs-on: ubuntu-latest steps: - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} # The github-release action expects a `LATEST` value of a string of either '1' or '0' - if: ${{ github.event_name == 'workflow_dispatch' }} run: | @@ -58,7 +51,6 @@ jobs: echo "version: $VERSION" echo "release_branch=$(echo $VERSION | sed -s 's/^v/release-/g')" >> "$GITHUB_OUTPUT" - echo "token=${{ steps.generate_token.outputs.token }}" >> "$GITHUB_OUTPUT" echo "dry_run=$DRY_RUN" >> "$GITHUB_OUTPUT" echo "latest=$LATEST" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT" @@ -66,42 +58,46 @@ jobs: name: Create next release branch (Grafana) needs: setup uses: ./.github/workflows/create-next-release-branch.yml + secrets: + GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana' source: ${{ needs.setup.outputs.release_branch }} - secrets: - token: ${{ needs.setup.outputs.token }} create_next_release_branch_enterprise: name: Create next release branch (Grafana Enterprise) needs: setup uses: ./.github/workflows/create-next-release-branch.yml - with: - ownerRepo: 'grafana/grafana' - source: ${{ needs.setup.outputs.release_branch }} secrets: - token: ${{ needs.setup.outputs.token }} + GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + with: + ownerRepo: 'grafana/grafana-enterprise' + source: ${{ needs.setup.outputs.release_branch }} migrate_prs_grafana: needs: - setup - create_next_release_branch_grafana uses: ./.github/workflows/migrate-prs.yml + secrets: + GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana' from: ${{ needs.setup.outputs.release_branch }} to: ${{ needs.create_next_release_branch_grafana.outputs.branch }} - secrets: - token: ${{ needs.setup.outputs.token }} migrate_prs_enterprise: needs: - setup - create_next_release_branch_enterprise uses: ./.github/workflows/migrate-prs.yml + secrets: + GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana-enterprise' from: ${{ needs.setup.outputs.release_branch }} to: ${{ needs.create_next_release_branch_enterprise.outputs.branch }} - secrets: - token: ${{ needs.setup.outputs.token }} post_changelog_on_forum: needs: setup uses: ./.github/workflows/community-release.yml From a51e673f2e5f94df37ba63ed2b8d0dd11f2fade9 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:59:08 -0700 Subject: [PATCH 16/34] Release: update changelog for 11.1.12 (#100900) Update changelog Co-authored-by: github-actions[bot] --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 956a074f98b..8c7ba477f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ + + +# 11.1.12 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#99917](https://github.com/grafana/grafana/pull/99917), [@DanCech](https://github.com/DanCech) + +### Bug fixes + +- **Azure:** Correctly set application insights resource values [#99595](https://github.com/grafana/grafana/pull/99595), [@aangelisc](https://github.com/aangelisc) + + # 11.0.11 (2025-02-18) From 561331e1a4c984ad52d07bd5f22a36860773b30e Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:01:57 -0700 Subject: [PATCH 17/34] Release: update changelog for 11.2.7 (#100912) Update changelog Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c7ba477f5f..e768acb7e18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ + + +# 11.2.7 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#99922](https://github.com/grafana/grafana/pull/99922), [@DanCech](https://github.com/DanCech) + +### Bug fixes + +- **Azure:** Correctly set application insights resource values [#99596](https://github.com/grafana/grafana/pull/99596), [@aangelisc](https://github.com/aangelisc) + + # 11.1.12 (2025-02-18) From ddae22a1e382739cbed2356ec3355e35d7b26ab4 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:03:46 -0700 Subject: [PATCH 18/34] Release: update changelog for 11.3.4 (#100927) Update changelog Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e768acb7e18..813b6bf8e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ + + +# 11.3.4 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#99923](https://github.com/grafana/grafana/pull/99923), [@DanCech](https://github.com/DanCech) + +### Bug fixes + +- **Auth:** Fix redirect with JWT auth URL login [#100495](https://github.com/grafana/grafana/pull/100495), [@mgyongyosi](https://github.com/mgyongyosi) +- **Azure:** Correctly set application insights resource values [#99597](https://github.com/grafana/grafana/pull/99597), [@aangelisc](https://github.com/aangelisc) +- **Dashboards:** Bring back scripted dashboards [#100627](https://github.com/grafana/grafana/pull/100627), [@dprokop](https://github.com/dprokop) +- **Plugin Metrics:** Eliminate data race in plugin metrics middleware [#100076](https://github.com/grafana/grafana/pull/100076), [@clord](https://github.com/clord) + + # 11.2.7 (2025-02-18) From fa9e08eb3261558ea1cdd8ca78080056b5fc2e40 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:04:53 -0700 Subject: [PATCH 19/34] Release: update changelog for 11.4.2 (#100915) * Update changelog * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 813b6bf8e78..ab04155af5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ + + +# 11.4.2 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#99924](https://github.com/grafana/grafana/pull/99924), [@DanCech](https://github.com/DanCech) + +### Bug fixes + +- **Auth:** Fix redirect with JWT auth URL login [#100494](https://github.com/grafana/grafana/pull/100494), [@mgyongyosi](https://github.com/mgyongyosi) +- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#100585](https://github.com/grafana/grafana/pull/100585), [@kalleep](https://github.com/kalleep) +- **Azure:** Correctly set application insights resource values [#99598](https://github.com/grafana/grafana/pull/99598), [@aangelisc](https://github.com/aangelisc) +- **Dashboards:** Bring back scripted dashboards [#100629](https://github.com/grafana/grafana/pull/100629), [@dprokop](https://github.com/dprokop) +- **Plugin Metrics:** Eliminate data race in plugin metrics middleware [#100077](https://github.com/grafana/grafana/pull/100077), [@clord](https://github.com/clord) +- **RBAC:** Don't check folder access if `annotationPermissionUpdate` FT is enabled [#100116](https://github.com/grafana/grafana/pull/100116), [@IevaVasiljeva](https://github.com/IevaVasiljeva) + + # 11.3.4 (2025-02-18) From ef15410e9e3bf5433a29e23ba3fdbfc85c7509a9 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:06:09 -0700 Subject: [PATCH 20/34] Release: update changelog for 11.5.2 (#100917) Update changelog Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab04155af5c..5b7107fc70a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ + + +# 11.5.2 (2025-02-18) + +### Features and enhancements + +- **Docker:** Use our own glibc 2.40 binaries [#99918](https://github.com/grafana/grafana/pull/99918), [@DanCech](https://github.com/DanCech) +- **TransformationFilter:** Include transformation outputs in transformation filtering options [#99878](https://github.com/grafana/grafana/pull/99878), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov) +- **grafana-ui:** Update InlineField error prop type to React.ReactNode [#100373](https://github.com/grafana/grafana/pull/100373), [@Clarity-89](https://github.com/Clarity-89) + +### Bug fixes + +- **Alerting:** Allow specifying uid for new rules added to groups [#100450](https://github.com/grafana/grafana/pull/100450), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Alerting:** Allow specifying uid for new rules added to groups [#100450](https://github.com/grafana/grafana/pull/100450), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Alerting:** Call RLock() before reading sendAlertsTo map [#99880](https://github.com/grafana/grafana/pull/99880), [@santihernandezc](https://github.com/santihernandezc) +- **Auth:** Fix redirect with JWT auth URL login [#100355](https://github.com/grafana/grafana/pull/100355), [@mgyongyosi](https://github.com/mgyongyosi) +- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#100582](https://github.com/grafana/grafana/pull/100582), [@kalleep](https://github.com/kalleep) +- **Azure:** Correctly set application insights resource values [#99599](https://github.com/grafana/grafana/pull/99599), [@aangelisc](https://github.com/aangelisc) +- **CodeEditor:** Fix cursor alignment [#99863](https://github.com/grafana/grafana/pull/99863), [@ashharrison90](https://github.com/ashharrison90) +- **DashboardList:** Throttle the re-renders [#100046](https://github.com/grafana/grafana/pull/100046), [@bfmatei](https://github.com/bfmatei) +- **Dashboards:** Bring back scripted dashboards [#100633](https://github.com/grafana/grafana/pull/100633), [@dprokop](https://github.com/dprokop) +- **Plugin Metrics:** Eliminate data race in plugin metrics middleware [#100078](https://github.com/grafana/grafana/pull/100078), [@clord](https://github.com/clord) +- **RBAC:** Don't check folder access if `annotationPermissionUpdate` FT is enabled [#100117](https://github.com/grafana/grafana/pull/100117), [@IevaVasiljeva](https://github.com/IevaVasiljeva) + + # 11.4.2 (2025-02-18) From 883f3c5ce2e05d5a568d54f65f5e4d63823956d2 Mon Sep 17 00:00:00 2001 From: Charandas <542168+charandas@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:14:22 -0800 Subject: [PATCH 21/34] K8s: add a bool setting to select request header auth versus enterprise behavior (#100877) --- pkg/services/apiserver/aggregator/aggregator.go | 16 +++++++++------- .../apiserver/options/kube-aggregator.go | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/services/apiserver/aggregator/aggregator.go b/pkg/services/apiserver/aggregator/aggregator.go index 67611fc3a71..76a446c8990 100644 --- a/pkg/services/apiserver/aggregator/aggregator.go +++ b/pkg/services/apiserver/aggregator/aggregator.go @@ -57,7 +57,8 @@ import ( // making sure we only register metrics once into legacy registry var registerIntoLegacyRegistryOnce sync.Once -func readCABundlePEM(path string, devMode bool) ([]byte, error) { +//nolint:unused +func _readCABundlePEM(path string, devMode bool) ([]byte, error) { if devMode { return nil, nil } @@ -128,8 +129,6 @@ func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig generi }, ExtraConfig: aggregatorapiserver.ExtraConfig{ DisableRemoteAvailableConditionController: true, - ProxyClientCertFile: commandOptions.KubeAggregatorOptions.ProxyClientCertFile, - ProxyClientKeyFile: commandOptions.KubeAggregatorOptions.ProxyClientKeyFile, // NOTE: while ProxyTransport can be skipped in the configuration, it allows honoring // DISABLE_HTTP2, HTTPS_PROXY and NO_PROXY env vars as needed ProxyTransport: createProxyTransport(), @@ -137,6 +136,13 @@ func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig generi }, } + if commandOptions.KubeAggregatorOptions.LegacyClientCertAuth { + // NOTE: the availability controller below is a bit different and uses the cert/key pair regardless + // of the legacy bool, this is because we are still using that for discovery requests + aggregatorConfig.ExtraConfig.ProxyClientCertFile = commandOptions.KubeAggregatorOptions.ProxyClientCertFile + aggregatorConfig.ExtraConfig.ProxyClientKeyFile = commandOptions.KubeAggregatorOptions.ProxyClientKeyFile + } + if err := commandOptions.KubeAggregatorOptions.ApplyTo(aggregatorConfig, commandOptions.RecommendedOptions.Etcd); err != nil { return nil, err } @@ -152,10 +158,6 @@ func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig generi return NewConfig(aggregatorConfig, sharedInformerFactory, []builder.APIGroupBuilder{serviceAPIBuilder}, nil), nil } - _, err = readCABundlePEM(commandOptions.KubeAggregatorOptions.APIServiceCABundleFile, commandOptions.ExtraOptions.DevMode) - if err != nil { - return nil, err - } remoteServices, err := ReadRemoteServices(commandOptions.KubeAggregatorOptions.RemoteServicesFile) if err != nil { return nil, err diff --git a/pkg/services/apiserver/options/kube-aggregator.go b/pkg/services/apiserver/options/kube-aggregator.go index ced1599729c..20e48c7a7ed 100644 --- a/pkg/services/apiserver/options/kube-aggregator.go +++ b/pkg/services/apiserver/options/kube-aggregator.go @@ -23,6 +23,7 @@ type KubeAggregatorOptions struct { AlternateDNS []string ProxyClientCertFile string ProxyClientKeyFile string + LegacyClientCertAuth bool RemoteServicesFile string APIServiceCABundleFile string } @@ -46,6 +47,9 @@ func (o *KubeAggregatorOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.ProxyClientKeyFile, "proxy-client-key-file", o.ProxyClientKeyFile, "path to proxy client key file") + + fs.BoolVar(&o.LegacyClientCertAuth, "legacy_client_cert_auth", true, + "whether to use legacy client cert auth") } func (o *KubeAggregatorOptions) Validate() []error { From cb05ead1b23ebf4365a28f433147dcfd7ca016e1 Mon Sep 17 00:00:00 2001 From: Adam Bannach <113929542+abannachGrafana@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:47:16 -0600 Subject: [PATCH 22/34] chore: add react-loading-skeleton to @grafana/runtime (#100941) --- packages/grafana-runtime/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 33b457d4340..90ca6774646 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -44,6 +44,7 @@ "@grafana/ui": "11.6.0-pre", "history": "4.10.1", "lodash": "4.17.21", + "react-loading-skeleton": "3.5.0", "react-use": "17.6.0", "rxjs": "7.8.1", "tslib": "2.8.1" diff --git a/yarn.lock b/yarn.lock index 80baf691c9a..c54b1d4815a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3701,6 +3701,7 @@ __metadata: lodash: "npm:4.17.21" react: "npm:18.3.1" react-dom: "npm:18.3.1" + react-loading-skeleton: "npm:3.5.0" react-use: "npm:17.6.0" rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" From d09ed251f67998255ce6323d415008b018d0a446 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Wed, 19 Feb 2025 01:11:31 -0700 Subject: [PATCH 23/34] k8s: unified search ui: fix url (#100939) --- public/app/features/search/service/unified.test.ts | 6 +++--- public/app/features/search/service/unified.ts | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/public/app/features/search/service/unified.test.ts b/public/app/features/search/service/unified.test.ts index 8841418cd5a..f013ca91ca7 100644 --- a/public/app/features/search/service/unified.test.ts +++ b/public/app/features/search/service/unified.test.ts @@ -146,7 +146,7 @@ describe('Unified Storage Searcher', () => { folder: 'General', tags: ['monitoring', 'performance'], field: { errors_today: 1 }, - url: '/dashboards/1', + url: '/dashboards/1/main-dashboard-title', }, { resource: 'dashboard', @@ -156,7 +156,7 @@ describe('Unified Storage Searcher', () => { folder: 'General', tags: ['monitoring', 'performance'], field: { errors_today: 2 }, - url: '/dashboards/1', + url: '/dashboards/1/main-dashboard-title', }, ]; @@ -184,7 +184,7 @@ describe('Unified Storage Searcher', () => { folder: 'General', tags: ['monitoring', 'performance'], field: { errors_today: 1 }, - url: '/dashboards/1', + url: '/dashboards/1/main-dashboard-title', }, ]; diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts index c1755c10517..2746ca81748 100644 --- a/public/app/features/search/service/unified.ts +++ b/public/app/features/search/service/unified.ts @@ -3,6 +3,7 @@ import { isEmpty } from 'lodash'; import { DataFrame, DataFrameView, getDisplayProcessor, SelectableValue, toDataFrame } from '@grafana/data'; import { config, getBackendSrv } from '@grafana/runtime'; import { TermCount } from 'app/core/components/TagFilter/TagFilter'; +import kbn from 'app/core/utils/kbn'; import { getAPINamespace } from '../../../api/utils'; @@ -321,7 +322,7 @@ export function toDashboardResults(rsp: SearchAPIResponse, sort: string): DataFr return { ...hit, uid: hit.name, - url: toURL(hit.resource, hit.name), + url: toURL(hit.resource, hit.name, hit.title), tags: hit.tags || [], folder: hit.folder || 'general', location, @@ -369,7 +370,7 @@ async function loadLocationInfo(): Promise> { locationInfo[hit.name] = { name: hit.title, kind: 'folder', - url: toURL('folders', hit.name), + url: toURL('folders', hit.name, hit.title), }; } return locationInfo; @@ -377,9 +378,10 @@ async function loadLocationInfo(): Promise> { return rsp; } -function toURL(resource: string, name: string): string { +function toURL(resource: string, name: string, title: string): string { if (resource === 'folders') { return `/dashboards/f/${name}`; } - return `/d/${name}`; + const slug = kbn.slugifyForUrl(title); + return `/d/${name}/${slug}`; } From 8577958edb7793208810c797e08b78a61ff05775 Mon Sep 17 00:00:00 2001 From: Robert Goltz Date: Wed, 19 Feb 2025 09:28:31 +0100 Subject: [PATCH 24/34] Bump github.com/crewjam/saml from 0.4.13 to 0.4.14 (#100784) chore: bump github.com/crewjam/saml from 0.4.13 to 0.4.14 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 05e98b520f3..b1db30c448c 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group github.com/bwmarrin/snowflake v0.3.0 // @grafan/grafana-app-platform-squad github.com/centrifugal/centrifuge v0.33.3 // @grafana/grafana-app-platform-squad - github.com/crewjam/saml v0.4.13 // @grafana/identity-access-team + github.com/crewjam/saml v0.4.14 // @grafana/identity-access-team github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 // @grafana/grafana-datasources-core-services github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // @grafana/grafana-datasources-core-services From e93ff154df6037a13cd6aae5cb5df347f1ef28a8 Mon Sep 17 00:00:00 2001 From: Ieva Date: Wed, 19 Feb 2025 08:37:32 +0000 Subject: [PATCH 25/34] Chore: bump authlib version (#100879) * bump authlib version * update other authlib deps --- go.mod | 2 +- go.sum | 4 ++-- pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 4 ++-- pkg/storage/unified/apistore/go.mod | 2 +- pkg/storage/unified/apistore/go.sum | 4 ++-- pkg/storage/unified/resource/go.mod | 2 +- pkg/storage/unified/resource/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index b1db30c448c..7e6f89a19df 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 468bdb10310..8155d19f17a 100644 --- a/go.sum +++ b/go.sum @@ -1513,8 +1513,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 538067db46a..98883d4bd54 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.23.1 require ( - github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.32.1 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 1a280d681df..70206eea956 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -32,8 +32,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 3f0c3be423e..5be50a3d587 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -193,7 +193,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // indirect - github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // indirect + github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 073beaffb31..d85a2fc49f8 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -568,8 +568,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 4ee8027f27d..c9e075baf93 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -11,7 +11,7 @@ replace ( require ( github.com/fullstorydev/grpchan v1.1.1 github.com/google/uuid v1.6.0 - github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc + github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 327e5de0cfa..57adef7935d 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -399,8 +399,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg= -github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= +github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= From cf624a6cf0eda829a7220893d03b2b75c41f2798 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:54:01 +0000 Subject: [PATCH 26/34] Update `make docs` procedure (#100945) Co-authored-by: grafanabot Co-authored-by: Jack Baldry --- docs/docs.mk | 4 +++- docs/make-docs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/docs.mk b/docs/docs.mk index 68a00fcf6e0..dfb6e9e501c 100644 --- a/docs/docs.mk +++ b/docs/docs.mk @@ -121,7 +121,9 @@ update: ## Fetch the latest version of this Makefile and the `make-docs` script curl -s -LO https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs chmod +x make-docs -.PHONY: topic/% +# ls static/templates/ | sed 's/-template\.md//' | xargs +TOPIC_TYPES := concept multiple-tasks reference section task tutorial visualization +.PHONY: $(patsubst %,topic/%,$(TOPIC_TYPES)) topic/%: ## Create a topic from the Writers' Toolkit template. Specify the topic type as the target, for example, `make topic/task TOPIC_PATH=sources/my-new-topic.md`. topic/%: $(if $(TOPIC_PATH),,$(error "You must set the TOPIC_PATH variable to the path where the $(@F) topic will be created. For example: make $(@) TOPIC_PATH=sources/my-new-topic.md")) diff --git a/docs/make-docs b/docs/make-docs index a81ec7530f3..80c89aea7ce 100755 --- a/docs/make-docs +++ b/docs/make-docs @@ -8,6 +8,12 @@ # [Semantic versioning](https://semver.org/) is used to help the reader identify the significance of changes. # Changes are relevant to this script and the support docs.mk GNU Make interface. # +# ## 8.5.1 (2025-02-18) +# +# ### Fixed +# +# - PHONY declaration for topic/ targets. +# # ## 8.5.0 (2025-02-13) # # ### Added From 1fdb36022d87935bd30f73bd46fde724431b87d7 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:58:49 +0100 Subject: [PATCH 27/34] Alerting docs: fix tutorial URL (#100954) --- .../fundamentals/notifications/notification-policies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/fundamentals/notifications/notification-policies.md b/docs/sources/alerting/fundamentals/notifications/notification-policies.md index 48a4bd70e37..ae8d53f1520 100644 --- a/docs/sources/alerting/fundamentals/notifications/notification-policies.md +++ b/docs/sources/alerting/fundamentals/notifications/notification-policies.md @@ -76,7 +76,7 @@ Each policy consists of a set of label matchers (0 or more) that specify which a {{< figure src="/media/docs/alerting/notification-routing.png" max-width="750px" caption="Matching alert instances with notification policies" alt="Example of a notification policy tree" >}} {{< admonition type="tip" >}} -For a practical example of routing with notification policies, refer to our [Getting Started with Alert Instances and Notification Routing tutorial](https://grafana.com/tutorials/alerting-get-started-pt4/). +For a practical example of routing with notification policies, refer to our [Getting Started with Alert Instances and Notification Routing tutorial](https://grafana.com/tutorials/alerting-get-started-pt2/). {{< /admonition >}} ## Routing From 953263f91b187abe3638264d0df02eb000bd7cca Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:00:07 +0100 Subject: [PATCH 28/34] Alerting notification preview: Remove private annotations and labels from alert instance payload (#100410) --- .../components/receivers/AlertInstanceModalSelector.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx b/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx index 05ace6725db..cd3e7c5d284 100644 --- a/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx +++ b/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx @@ -60,6 +60,13 @@ export function AlertInstanceModalSelector({ if (!rules[instance.labels.alertname]) { rules[instance.labels.alertname] = []; } + const filteredAnnotations = Object.fromEntries( + Object.entries(instance.annotations).filter(([key]) => !key.startsWith('__')) + ); + const filteredLabels = Object.fromEntries( + Object.entries(instance.labels).filter(([key]) => !key.startsWith('__')) + ); + instance = { ...instance, annotations: filteredAnnotations, labels: filteredLabels }; rules[instance.labels.alertname].push(instance); }); } From af261af7a73f2b808681f6da703b48034f1cd98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Wed, 19 Feb 2025 11:02:16 +0100 Subject: [PATCH 29/34] UnifyHistory: Add styles from the docked POC (#100381) --- .../components/AppChrome/AppChromeService.tsx | 6 +- .../AppChrome/History/HistoryContainer.tsx | 2 +- .../AppChrome/History/HistoryWrapper.tsx | 282 +++++++++++------- public/locales/en-US/grafana.json | 2 + public/locales/pseudo-LOCALE/grafana.json | 2 + 5 files changed, 183 insertions(+), 111 deletions(-) diff --git a/public/app/core/components/AppChrome/AppChromeService.tsx b/public/app/core/components/AppChrome/AppChromeService.tsx index 85f2a5cdd83..3a0690b8abd 100644 --- a/public/app/core/components/AppChrome/AppChromeService.tsx +++ b/public/app/core/components/AppChrome/AppChromeService.tsx @@ -147,10 +147,10 @@ export class AppChromeService { const lastEntry = entries[0]; const newEntry = { name: newPageNav.text, views: [], breadcrumbs, time: Date.now(), url: window.location.href }; - const isSameUrl = lastEntry && newEntry.url === lastEntry.url; + const isSamePath = lastEntry && newEntry.url.split('?')[0] === lastEntry.url.split('?')[0]; - // To avoid adding an entry with the same url twice, we always use the latest one - if (isSameUrl) { + // To avoid adding an entry with the same path twice, we always use the latest one + if (isSamePath) { entries[0] = newEntry; } else { entries = [newEntry, ...entries]; diff --git a/public/app/core/components/AppChrome/History/HistoryContainer.tsx b/public/app/core/components/AppChrome/History/HistoryContainer.tsx index 5d8482a3697..917be510f45 100644 --- a/public/app/core/components/AppChrome/History/HistoryContainer.tsx +++ b/public/app/core/components/AppChrome/History/HistoryContainer.tsx @@ -60,7 +60,7 @@ export function HistoryContainer() { onToggleShowHistoryDrawer(false)} /> diff --git a/public/app/core/components/AppChrome/History/HistoryWrapper.tsx b/public/app/core/components/AppChrome/History/HistoryWrapper.tsx index 687f631fd8c..d1a873b61cc 100644 --- a/public/app/core/components/AppChrome/History/HistoryWrapper.tsx +++ b/public/app/core/components/AppChrome/History/HistoryWrapper.tsx @@ -1,9 +1,9 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import moment from 'moment'; import { useState } from 'react'; import { FieldType, GrafanaTheme2, store } from '@grafana/data'; -import { Button, Card, IconButton, Space, Stack, Text, useStyles2, Box, Sparkline, useTheme2 } from '@grafana/ui'; +import { Button, Card, IconButton, Space, Stack, Text, useStyles2, Box, Sparkline, useTheme2, Icon } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { HISTORY_LOCAL_STORAGE_KEY } from '../AppChromeService'; @@ -32,34 +32,38 @@ export function HistoryWrapper({ onClose }: { onClose: () => void }) { acc[key] = [...(acc[key] || []), entry]; return acc; }, {}); - + const styles = useStyles2(getStyles); return ( {Object.keys(hist).map((entries, date) => { return ( - - {entries} - - {hist[entries].map((entry, index) => { - return ( - onClose()} - /> - ); - })} + + {entries} + +
+ {hist[entries].map((entry, index) => { + return ( + onClose()} + /> + ); + })} +
); })}
{history.length > numItemsToShow && ( - + + + )}
); @@ -78,7 +82,9 @@ function HistoryEntryAppView({ entry, isSelected, onClick }: ItemProps) { const expandedLabel = isExpanded ? t('nav.history-wrapper.collapse', 'Collapse') : t('nav.history-wrapper.expand', 'Expand'); - + const entryIconLabel = isExpanded + ? t('nav.history-wrapper.icon-selected', 'Selected Entry') + : t('nav.history-wrapper.icon-unselected', 'Normal Entry'); const selectedViewTime = isSelected && entry.views.find((entry) => { @@ -86,119 +92,181 @@ function HistoryEntryAppView({ entry, isSelected, onClick }: ItemProps) { })?.time; return ( - - - {views.length > 0 ? ( - setIsExpanded(!isExpanded)} - aria-label={expandedLabel} - className={styles.iconButton} + + + + {views.length > 0 ? ( + setIsExpanded(!isExpanded)} + aria-label={expandedLabel} + className={styles.iconButton} + /> + ) : ( + + )} + - ) : ( - - )} - - { - store.setObject('CLICKING_HISTORY', true); - onClick(); - }} - href={url} - isCompact={true} - className={isSelected ? undefined : styles.card} - > - -
- {breadcrumbs.map((breadcrumb, index) => ( - - {breadcrumb.text} {index !== breadcrumbs.length - 1 ? '> ' : ''} - - ))} -
- {moment(time).format('h:mm A')} - {sparklineData && ( - { + store.setObject('CLICKING_HISTORY', true); + onClick(); + }} + href={url} + isCompact={true} + className={isSelected ? styles.card : cx(styles.card, styles.cardSelected)} + > + +
+ {breadcrumbs.map((breadcrumb, index) => ( + + {breadcrumb.text} {index !== breadcrumbs.length - 1 ? '> ' : ''} + + ))} +
+ + {moment(time).format('h:mm A')} + + {sparklineData && ( + - )} -
-
+ }} + /> + )} +
+ +
+ {isExpanded && ( +
+ {views.map((view, index) => { + return ( + { + store.setObject('CLICKING_HISTORY', true); + onClick(); + }} + isCompact={true} + className={view.time === selectedViewTime ? undefined : styles.subCard} + > + + {view.name} + {view.description && ( + + {view.description} + + )} + + + ); + })} +
+ )}
- {isExpanded && ( -
- {views.map((view, index) => { - return ( - { - store.setObject('CLICKING_HISTORY', true); - onClick(); - }} - isCompact={true} - className={view.time === selectedViewTime ? undefined : styles.card} - > - - {view.name} - {view.description && ( - - {view.description} - - )} - - - ); - })} -
- )} -
+ ); } const getStyles = (theme: GrafanaTheme2) => { return { card: css({ + label: 'card', + background: 'none', + margin: theme.spacing(0.5, 0), + }), + cardSelected: css({ + label: 'card-selected', background: 'none', }), - iconButton: css({ + subCard: css({ + label: 'subcard', + background: 'none', margin: 0, }), + iconButton: css({ + label: 'expand-button', + margin: 0, + }), + iconButtonCircle: css({ + label: 'blue-circle-icon', + margin: 0, + background: theme.colors.background.primary, + fill: theme.colors.primary.main, + cursor: 'default', + '&:hover:before': { + background: 'none', + }, + //Need this to place the icon on the line, otherwise the line will appear on top of the icon + zIndex: 0, + }), + iconButtonDot: css({ + label: 'blue-dot-icon', + margin: 0, + color: theme.colors.primary.main, + border: theme.shape.radius.circle, + cursor: 'default', + '&:hover:before': { + background: 'none', + }, + //Need this to place the icon on the line, otherwise the line will appear on top of the icon + zIndex: 0, + }), expanded: css({ + label: 'expanded', display: 'flex', flexDirection: 'column', - marginLeft: theme.spacing(5), + marginLeft: theme.spacing(6), gap: theme.spacing(1), position: 'relative', '&:before': { content: '""', position: 'absolute', - left: theme.spacing(-2), + left: 0, top: 0, height: '100%', width: '1px', background: theme.colors.border.weak, }, }), + timeline: css({ + label: 'timeline', + position: 'relative', + height: '100%', + width: '100%', + paddingLeft: theme.spacing(2), + '&:before': { + content: '""', + position: 'absolute', + left: theme.spacing(5.75), + top: 0, + height: '100%', + width: '1px', + borderLeft: `1px dashed ${theme.colors.border.strong}`, + }, + }), }; }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 1416699e4d5..e80a59d9129 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2536,6 +2536,8 @@ "history-wrapper": { "collapse": "Collapse", "expand": "Expand", + "icon-selected": "Selected Entry", + "icon-unselected": "Normal Entry", "show-more": "Show more", "today": "Today", "yesterday": "Yesterday" diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 39128a2cf53..43096c5ce25 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -2536,6 +2536,8 @@ "history-wrapper": { "collapse": "Cőľľäpşę", "expand": "Ēχpäʼnđ", + "icon-selected": "Ŝęľęčŧęđ Ēʼnŧřy", + "icon-unselected": "Ńőřmäľ Ēʼnŧřy", "show-more": "Ŝĥőŵ mőřę", "today": "Ŧőđäy", "yesterday": "Ÿęşŧęřđäy" From e07b6efbc493d625c73dfa9730e949671160f2f8 Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:03:37 +0000 Subject: [PATCH 30/34] ImportDashboardForm: Add more meaningful error message for Editor user (#100581) return error message when getting 403 during dashboard import --- public/app/features/manage-dashboards/utils/validation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/app/features/manage-dashboards/utils/validation.ts b/public/app/features/manage-dashboards/utils/validation.ts index 7e4b1d9089e..d70e22ed975 100644 --- a/public/app/features/manage-dashboards/utils/validation.ts +++ b/public/app/features/manage-dashboards/utils/validation.ts @@ -53,6 +53,12 @@ export const validateUid = (value: string) => { }) .catch((error) => { error.isHandled = true; + + // when Editor user tries to import admin only dashboard (with same uid) he gets an unhelpful 403 error + // therefore handling this use case to return some indication of whats wrong + if (error.status === 403) { + return 'Dashboard with the same UID already exists'; + } return true; }); }; From 16c389a79ab9b49f87dab21d1533eb7edbc4324a Mon Sep 17 00:00:00 2001 From: Karl Persson <23356117+kalleep@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:41:18 +0100 Subject: [PATCH 31/34] Auth: Use authinfo instead (#100957) Use authinfo instead --- pkg/storage/unified/apistore/prepare.go | 19 ++++++++++--------- pkg/storage/unified/apistore/prepare_test.go | 9 +++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/pkg/storage/unified/apistore/prepare.go b/pkg/storage/unified/apistore/prepare.go index b371444c66a..e4bb5ed7d18 100644 --- a/pkg/storage/unified/apistore/prepare.go +++ b/pkg/storage/unified/apistore/prepare.go @@ -3,17 +3,18 @@ package apistore import ( "bytes" "context" + "errors" "fmt" "math" "time" "github.com/google/uuid" + authtypes "github.com/grafana/authlib/types" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/storage" "k8s.io/klog/v2" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -37,9 +38,9 @@ func formatBytes(numBytes int) string { // Called on create func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime.Object) ([]byte, error) { - user, err := identity.GetRequester(ctx) - if err != nil { - return nil, err + info, ok := authtypes.AuthInfoFrom(ctx) + if !ok { + return nil, errors.New("missing auth info") } obj, err := utils.MetaAccessor(newObject) @@ -80,7 +81,7 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime obj.SetRepositoryInfo(repo) obj.SetUpdatedBy("") obj.SetUpdatedTimestamp(nil) - obj.SetCreatedBy(user.GetUID()) + obj.SetCreatedBy(info.GetUID()) var buf bytes.Buffer if err = s.codec.Encode(newObject, &buf); err != nil { @@ -91,9 +92,9 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime // Called on update func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runtime.Object, previousObject runtime.Object) ([]byte, error) { - user, err := identity.GetRequester(ctx) - if err != nil { - return nil, err + info, ok := authtypes.AuthInfoFrom(ctx) + if !ok { + return nil, errors.New("missing auth info") } obj, err := utils.MetaAccessor(updateObject) @@ -141,7 +142,7 @@ func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runti return nil, err } obj.SetRepositoryInfo(repo) - obj.SetUpdatedBy(user.GetUID()) + obj.SetUpdatedBy(info.GetUID()) obj.SetUpdatedTimestampMillis(time.Now().UnixMilli()) var buf bytes.Buffer diff --git a/pkg/storage/unified/apistore/prepare_test.go b/pkg/storage/unified/apistore/prepare_test.go index d24b31a5a91..18d87952615 100644 --- a/pkg/storage/unified/apistore/prepare_test.go +++ b/pkg/storage/unified/apistore/prepare_test.go @@ -6,10 +6,10 @@ import ( "time" "github.com/bwmarrin/snowflake" + authtypes "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/pkg/services/user" "github.com/stretchr/testify/require" "golang.org/x/exp/rand" "k8s.io/apimachinery/pkg/api/apitesting" @@ -32,12 +32,13 @@ func TestPrepareObjectForStorage(t *testing.T) { LargeObjectSupport: nil, }, } - ctx := identity.WithRequester(context.Background(), &user.SignedInUser{UserID: 1, UserUID: "user-uid"}) - t.Run("Error getting requester from context", func(t *testing.T) { + ctx := authtypes.WithAuthInfo(context.Background(), &identity.StaticRequester{UserID: 1, UserUID: "user-uid", Type: authtypes.TypeUser}) + + t.Run("Error getting auth info from context", func(t *testing.T) { _, err := s.prepareObjectForStorage(context.Background(), nil) require.Error(t, err) - require.Contains(t, err.Error(), "a Requester was not found in the context") + require.Contains(t, err.Error(), "missing auth info") }) t.Run("Error on missing name", func(t *testing.T) { From a897ec3426e1482a59e8e8bcf8b6f5c3daa634e9 Mon Sep 17 00:00:00 2001 From: Karl Persson <23356117+kalleep@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:46:48 +0100 Subject: [PATCH 32/34] Authn: grpc errors (#100951) * update authlib * Map to grpc status --- go.mod | 4 ++-- go.sum | 8 ++++---- go.work.sum | 6 ++++++ pkg/apimachinery/go.mod | 4 ++-- pkg/apimachinery/go.sum | 8 ++++---- pkg/apiserver/go.mod | 2 +- pkg/apiserver/go.sum | 4 ++-- pkg/services/authn/grpcutils/grpc_authenticator.go | 8 +++++++- pkg/storage/unified/apistore/go.mod | 4 ++-- pkg/storage/unified/apistore/go.sum | 8 ++++---- pkg/storage/unified/resource/go.mod | 4 ++-- pkg/storage/unified/resource/go.sum | 8 ++++---- 12 files changed, 40 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 7e6f89a19df..5bd76947b45 100644 --- a/go.mod +++ b/go.mod @@ -72,8 +72,8 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 // @grafana/identity-access-team - github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team + github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // @grafana/grafana-backend-group diff --git a/go.sum b/go.sum index 8155d19f17a..70182a7389d 100644 --- a/go.sum +++ b/go.sum @@ -1513,10 +1513,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg= github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= diff --git a/go.work.sum b/go.work.sum index 49c8194f889..b532fdb1cde 100644 --- a/go.work.sum +++ b/go.work.sum @@ -605,6 +605,8 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I= github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw= github.com/grafana/cog v0.0.23 h1:/0CCJ24Z8XXM2DnboSd2FzoIswUroqIZzVr8oJWmMQs= @@ -1062,6 +1064,7 @@ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -1085,6 +1088,7 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1109,12 +1113,14 @@ gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250102185135-69823020774d h1:NZBSeFsuFS5YrgHMW/8xfTbzNXMshQPNgq2Yb7xipEs= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250102185135-69823020774d/go.mod h1:s4mHJ3FfG8P6A3O+gZ8TVqB3ufjOl9UG3ANCMMwCHmo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 98883d4bd54..6608d7f91b3 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,8 +3,8 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.23.1 require ( - github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 // @grafana/identity-access-team - github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team + github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.32.1 k8s.io/apiserver v0.32.1 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 70206eea956..864e82c4e31 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -32,10 +32,10 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 4676380219e..1c208fbbb0c 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.6 require ( github.com/google/go-cmp v0.6.0 - github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c + github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 github.com/grafana/grafana-app-sdk/logging v0.30.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 github.com/prometheus/client_golang v1.20.5 diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index b0c1e3d43de..921a0ec5c70 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -79,8 +79,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/grafana-app-sdk/logging v0.30.0 h1:K/P/bm7Cp7Di4tqIJ3EQz2+842JozQGRaz62r95ApME= github.com/grafana/grafana-app-sdk/logging v0.30.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs= diff --git a/pkg/services/authn/grpcutils/grpc_authenticator.go b/pkg/services/authn/grpcutils/grpc_authenticator.go index 692f9fbdea8..b85457dfaf9 100644 --- a/pkg/services/authn/grpcutils/grpc_authenticator.go +++ b/pkg/services/authn/grpcutils/grpc_authenticator.go @@ -12,7 +12,9 @@ import ( "github.com/grafana/authlib/types" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/grpcserver/interceptors" @@ -75,7 +77,11 @@ func newAuthenticator(auth authn.Authenticator, tracer tracing.Tracer) intercept info, err := auth.Authenticate(ctx, authn.NewGRPCTokenProvider(md)) if err != nil { span.RecordError(err) - return ctx, err + if authn.IsUnauthenticatedErr(err) { + return nil, status.Error(codes.Unauthenticated, err.Error()) + } + + return ctx, status.Error(codes.Internal, err.Error()) } // FIXME: Add attribute with service subject once https://github.com/grafana/authlib/issues/139 is closed. diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 5be50a3d587..6733e336ea4 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -14,7 +14,7 @@ exclude k8s.io/client-go v12.0.0+incompatible require ( github.com/bwmarrin/snowflake v0.3.0 github.com/google/uuid v1.6.0 - github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c + github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250121113133-e747350fee2d github.com/grafana/grafana/pkg/apiserver v0.0.0-20250121113133-e747350fee2d @@ -193,7 +193,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // indirect - github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 // indirect + github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index d85a2fc49f8..44e6bd2c091 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -568,10 +568,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg= github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index c9e075baf93..bd53491cb6b 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -11,8 +11,8 @@ replace ( require ( github.com/fullstorydev/grpchan v1.1.1 github.com/google/uuid v1.6.0 - github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 - github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c + github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 + github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible github.com/grafana/grafana-plugin-sdk-go v0.266.0 diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 57adef7935d..ca5165f2a1f 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -399,10 +399,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77 h1:iD5KKdyyjZjrRQ/eXiFkrFfNt2V3klyQ28zONj7oi2I= -github.com/grafana/authlib v0.0.0-20250218130339-1364fbcc4d77/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= -github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= +github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= +github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g= From 8646961c3ff809ac74847c351f73fbeeea408a51 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:47:55 +0100 Subject: [PATCH 33/34] Alerting docs: fix heading anchor (#100783) Co-authored-by: Jack Baldry --- .../manage-contact-points/integrations/webhook-notifier.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index 1a7598891a1..e7741ac3b56 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -50,7 +50,7 @@ To create a contact point with webhook integration, complete the following steps 1. Enter a name for the contact point. 1. From the **Integration** list, select **Webhook**. 1. In the **URL** field, copy in your Webhook URL. -1. (Optional) Configure [additional settings](#settings). +1. (Optional) Configure [additional settings](#webhook-settings). 1. Click **Save contact point**. For more details on contact points, including how to test them and enable notifications, refer to [Configure contact points](ref:configure-contact-points). From 011726c80f8d21d7cff12198cf4b0bff2a1df1fe Mon Sep 17 00:00:00 2001 From: Giuseppe Guerra Date: Wed, 19 Feb 2025 12:19:45 +0100 Subject: [PATCH 34/34] Plugins: Update DefaultSignature function signature in PluginSource interface (#100519) * Plugins CDN: Add support for signature type and signature org * Fall-back to valid signature if the config is not supported * Move CDN signature code to DefaultSignature * fix tests --- pkg/plugins/ifaces.go | 2 +- pkg/plugins/manager/fakes/fakes.go | 2 +- pkg/plugins/manager/signature/manifest.go | 2 +- pkg/plugins/manager/sources/source_local_disk.go | 2 +- pkg/plugins/manager/sources/sources_test.go | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index 0160fc1112b..368537db337 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -20,7 +20,7 @@ type Installer interface { type PluginSource interface { PluginClass(ctx context.Context) Class PluginURIs(ctx context.Context) []string - DefaultSignature(ctx context.Context) (Signature, bool) + DefaultSignature(ctx context.Context, pluginID string) (Signature, bool) } type FileStore interface { diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index d84de30e969..8cb762f94b8 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -494,7 +494,7 @@ func (s *FakePluginSource) PluginURIs(ctx context.Context) []string { return []string{} } -func (s *FakePluginSource) DefaultSignature(ctx context.Context) (plugins.Signature, bool) { +func (s *FakePluginSource) DefaultSignature(ctx context.Context, _ string) (plugins.Signature, bool) { if s.DefaultSignatureFunc != nil { return s.DefaultSignatureFunc(ctx) } diff --git a/pkg/plugins/manager/signature/manifest.go b/pkg/plugins/manager/signature/manifest.go index c363f888198..6d790c79873 100644 --- a/pkg/plugins/manager/signature/manifest.go +++ b/pkg/plugins/manager/signature/manifest.go @@ -141,7 +141,7 @@ func (s *Signature) ReadPluginManifestFromFS(ctx context.Context, pfs plugins.FS } func (s *Signature) Calculate(ctx context.Context, src plugins.PluginSource, plugin plugins.FoundPlugin) (plugins.Signature, error) { - if defaultSignature, exists := src.DefaultSignature(ctx); exists { + if defaultSignature, exists := src.DefaultSignature(ctx, plugin.JSONData.ID); exists { return defaultSignature, nil } diff --git a/pkg/plugins/manager/sources/source_local_disk.go b/pkg/plugins/manager/sources/source_local_disk.go index 4fddc464fea..fc20b45499f 100644 --- a/pkg/plugins/manager/sources/source_local_disk.go +++ b/pkg/plugins/manager/sources/source_local_disk.go @@ -30,7 +30,7 @@ func (s *LocalSource) PluginURIs(_ context.Context) []string { return s.paths } -func (s *LocalSource) DefaultSignature(_ context.Context) (plugins.Signature, bool) { +func (s *LocalSource) DefaultSignature(_ context.Context, _ string) (plugins.Signature, bool) { switch s.class { case plugins.ClassCore: return plugins.Signature{ diff --git a/pkg/plugins/manager/sources/sources_test.go b/pkg/plugins/manager/sources/sources_test.go index 3719b021a18..898aedff819 100644 --- a/pkg/plugins/manager/sources/sources_test.go +++ b/pkg/plugins/manager/sources/sources_test.go @@ -41,7 +41,7 @@ func TestSources_List(t *testing.T) { filepath.Join(testdata, "app", "plugins", "datasource"), filepath.Join(testdata, "app", "plugins", "panel"), }) - sig, exists := srcs[0].DefaultSignature(ctx) + sig, exists := srcs[0].DefaultSignature(ctx, "") require.True(t, exists) require.Equal(t, plugins.SignatureStatusInternal, sig.Status) require.Equal(t, plugins.SignatureType(""), sig.Type) @@ -51,7 +51,7 @@ func TestSources_List(t *testing.T) { require.Equal(t, srcs[1].PluginURIs(ctx), []string{ filepath.Join(testdata, "pluginRootWithDist", "datasource"), }) - sig, exists = srcs[1].DefaultSignature(ctx) + sig, exists = srcs[1].DefaultSignature(ctx, "") require.False(t, exists) require.Equal(t, plugins.Signature{}, sig) @@ -59,7 +59,7 @@ func TestSources_List(t *testing.T) { require.Equal(t, srcs[2].PluginURIs(ctx), []string{ filepath.Join(testdata, "pluginRootWithDist", "dist"), }) - sig, exists = srcs[2].DefaultSignature(ctx) + sig, exists = srcs[2].DefaultSignature(ctx, "") require.False(t, exists) require.Equal(t, plugins.Signature{}, sig) @@ -67,7 +67,7 @@ func TestSources_List(t *testing.T) { require.Equal(t, srcs[3].PluginURIs(ctx), []string{ filepath.Join(testdata, "pluginRootWithDist", "panel"), }) - sig, exists = srcs[3].DefaultSignature(ctx) + sig, exists = srcs[3].DefaultSignature(ctx, "") require.False(t, exists) require.Equal(t, plugins.Signature{}, sig) })