From 2e352ba4d6ba01881df3d5f62dedd9eee2000afb Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Fri, 26 Jan 2024 03:16:45 -0500 Subject: [PATCH 001/169] Docs(alerting): Document the `disable_provenance` attribute (#81290) We no longer need to pass a http header --- .../view-provisioned-resources/index.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/view-provisioned-resources/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/view-provisioned-resources/index.md index 5edb960bffc..ea434acf65c 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/view-provisioned-resources/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/view-provisioned-resources/index.md @@ -88,15 +88,18 @@ To enable editing of API-provisioned resources in the Grafana UI, add the `X-Dis To reset the notification policy tree to the default and unlock it for editing in the Grafana UI, use the `DELETE /api/v1/provisioning/policies` endpoint. -To pass the `X-Disable-Provenance` header from Terraform, add it to the `http_headers` field on the provider object: +In Terraform, you can use the `disable_provenance` attribute on alerting resources: ``` provider "grafana" { url = "http://grafana.example.com/" auth = var.grafana_auth - http_headers = { - "X-Disable-Provenance" = "true" - } +} + +resource "grafana_mute_timing" "mute_all" { + name = "mute all" + disable_provenance = true + intervals {} } ``` From 722b78f3e04d488c9b6d670f9509a472d4e1f528 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Fri, 26 Jan 2024 09:43:16 +0100 Subject: [PATCH 002/169] RBAC: Add userLogin filter to the permission search endpoint (#81137) * RBAC: Search add user login filter * Switch to a userService resolving instead * Remove unused error * Fallback to use the cache * account for userID filter * Account for the error * snake case * Add test cases * Add api tests * Fix return on error * Re-order imports --- pkg/api/folder_bench_test.go | 3 +- .../commands/conflict_user_command.go | 2 +- .../apis/datasource/standalone_services.go | 19 ++++- pkg/services/accesscontrol/accesscontrol.go | 14 ++++ pkg/services/accesscontrol/acimpl/service.go | 58 +++++++++++---- .../accesscontrol/acimpl/service_test.go | 74 ++++++++++++++++++- pkg/services/accesscontrol/api/api.go | 22 +++++- pkg/services/accesscontrol/api/api_test.go | 74 +++++++++++++++++++ .../oauthserver/oasimpl/service_test.go | 2 +- .../ngalert/migration/store/testing.go | 5 +- .../extsvcaccounts/service_test.go | 3 +- 11 files changed, 246 insertions(+), 30 deletions(-) diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 83f326e1d49..fa4896b62b8 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -46,6 +46,7 @@ import ( "github.com/grafana/grafana/pkg/services/team/teamimpl" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/userimpl" + "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web/webtest" @@ -439,7 +440,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog license := licensingtest.NewFakeLicensing() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() - acSvc := acimpl.ProvideOSSService(sc.cfg, acdb.ProvideService(sc.db), localcache.ProvideService(), features) + acSvc := acimpl.ProvideOSSService(sc.cfg, acdb.ProvideService(sc.db), localcache.ProvideService(), usertest.NewUserServiceFake(), features) quotaSrv := quotatest.New(false, nil) diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go index 8bccb53bdab..637ae88b75e 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -71,7 +71,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get feature management service", err) } - acService, err := acimpl.ProvideService(cfg, s, routing, nil, nil, featMgmt) + acService, err := acimpl.ProvideService(cfg, s, routing, nil, nil, nil, featMgmt) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get access control", err) } diff --git a/pkg/registry/apis/datasource/standalone_services.go b/pkg/registry/apis/datasource/standalone_services.go index f8028263fc2..085e25bc105 100644 --- a/pkg/registry/apis/datasource/standalone_services.go +++ b/pkg/registry/apis/datasource/standalone_services.go @@ -31,6 +31,7 @@ import ( "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/kmsproviders/osskmsproviders" "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/pluginsintegration/config" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/quota/quotaimpl" @@ -40,6 +41,8 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrations" "github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry" + "github.com/grafana/grafana/pkg/services/team/teamimpl" + "github.com/grafana/grafana/pkg/services/user/userimpl" "github.com/grafana/grafana/pkg/setting" ) @@ -73,11 +76,23 @@ func apiBuilderServices(cfg *setting.Cfg, pluginID string) ( kvStore := kvstore.ProvideService(sqlStore) featureToggles := featuremgmt.ProvideToggles(featureManager) - acimplService, err := acimpl.ProvideService(cfg, sqlStore, routeRegisterImpl, cacheService, accessControl, featureToggles) + bundleRegistry := bundleregistry.ProvideService() + + quota := quotaimpl.ProvideService(sqlStore, cfg) + orgService, err := orgimpl.ProvideService(sqlStore, cfg, quota) + if err != nil { + return nil, nil, nil, nil, err + } + teamService := teamimpl.ProvideService(sqlStore, cfg) + userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamService, cacheService, quota, bundleRegistry) + if err != nil { + return nil, nil, nil, nil, err + } + + acimplService, err := acimpl.ProvideService(cfg, sqlStore, routeRegisterImpl, cacheService, accessControl, userService, featureToggles) if err != nil { return nil, nil, nil, nil, err } - bundleRegistry := bundleregistry.ProvideService() usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, acimplService, bundleRegistry) if err != nil { return nil, nil, nil, nil, err diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index bb094a9702e..ab9a93e68f4 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -57,6 +57,7 @@ type SearchOptions struct { ActionPrefix string // Needed for the PoC v1, it's probably going to be removed. Action string Scope string + UserLogin string // Login for which to return information, if none is specified information is returned for all users. UserID int64 // ID for the user for which to return information, if none is specified information is returned for all users. wildcards Wildcards // private field computed based on the Scope } @@ -76,6 +77,19 @@ func (s *SearchOptions) Wildcards() []string { return s.wildcards } +func (s *SearchOptions) ResolveUserLogin(ctx context.Context, userSvc user.Service) error { + if s.UserLogin == "" { + return nil + } + // Resolve userLogin -> userID + dbUsr, err := userSvc.GetByLogin(ctx, &user.GetUserByLoginQuery{LoginOrEmail: s.UserLogin}) + if err != nil { + return err + } + s.UserID = dbUsr.ID + return nil +} + type SyncUserRolesCommand struct { UserID int64 // name of roles the user should have diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 13f10c43a97..cd10de3246f 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -42,8 +42,8 @@ var SharedWithMeFolderPermission = accesscontrol.Permission{ } func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegister, cache *localcache.CacheService, - accessControl accesscontrol.AccessControl, features featuremgmt.FeatureToggles) (*Service, error) { - service := ProvideOSSService(cfg, database.ProvideService(db), cache, features) + accessControl accesscontrol.AccessControl, userSvc user.Service, features featuremgmt.FeatureToggles) (*Service, error) { + service := ProvideOSSService(cfg, database.ProvideService(db), cache, userSvc, features) api.NewAccessControlAPI(routeRegister, accessControl, service, features).RegisterAPIEndpoints() if err := accesscontrol.DeclareFixedRoles(service, cfg); err != nil { @@ -63,14 +63,15 @@ func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegis return service, nil } -func ProvideOSSService(cfg *setting.Cfg, store store, cache *localcache.CacheService, features featuremgmt.FeatureToggles) *Service { +func ProvideOSSService(cfg *setting.Cfg, store store, cache *localcache.CacheService, userSvc user.Service, features featuremgmt.FeatureToggles) *Service { s := &Service{ - cfg: cfg, - store: store, - log: log.New("accesscontrol.service"), cache: cache, - roles: accesscontrol.BuildBasicRoleDefinitions(), + cfg: cfg, features: features, + log: log.New("accesscontrol.service"), + roles: accesscontrol.BuildBasicRoleDefinitions(), + store: store, + userSvc: userSvc, } return s @@ -88,13 +89,14 @@ type store interface { // Service is the service implementing role based access control. type Service struct { - log log.Logger - cfg *setting.Cfg - store store cache *localcache.CacheService + cfg *setting.Cfg + features featuremgmt.FeatureToggles + log log.Logger registrations accesscontrol.RegistrationList roles map[string]*accesscontrol.RoleDTO - features featuremgmt.FeatureToggles + store store + userSvc user.Service } func (s *Service) GetUsageStats(_ context.Context) map[string]any { @@ -243,8 +245,25 @@ func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs } // SearchUsersPermissions returns all users' permissions filtered by action prefixes -func (s *Service) SearchUsersPermissions(ctx context.Context, user identity.Requester, +func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { + if options.UserLogin != "" { + // Resolve userLogin -> userID + if err := options.ResolveUserLogin(ctx, s.userSvc); err != nil { + return nil, err + } + options.UserLogin = "" + } + if options.UserID > 0 { + // Reroute to the user specific implementation of search permissions + // because it leverages the user permission cache. + userPerms, err := s.SearchUserPermissions(ctx, usr.GetOrgID(), options) + if err != nil { + return nil, err + } + return map[int64][]accesscontrol.Permission{options.UserID: userPerms}, nil + } + timer := prometheus.NewTimer(metrics.MAccessSearchPermissionsSummary) defer timer.ObserveDuration() @@ -258,20 +277,20 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, user identity.Requ } } - usersRoles, err := s.store.GetUsersBasicRoles(ctx, nil, user.GetOrgID()) + usersRoles, err := s.store.GetUsersBasicRoles(ctx, nil, usr.GetOrgID()) if err != nil { return nil, err } // Get managed permissions (DB) - usersPermissions, err := s.store.SearchUsersPermissions(ctx, user.GetOrgID(), options) + usersPermissions, err := s.store.SearchUsersPermissions(ctx, usr.GetOrgID(), options) if err != nil { return nil, err } // helper to filter out permissions the signed in users cannot see canView := func() func(userID int64) bool { - siuPermissions := user.GetPermissions() + siuPermissions := usr.GetPermissions() if len(siuPermissions) == 0 { return func(_ int64) bool { return false } } @@ -329,8 +348,15 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) defer timer.ObserveDuration() + if searchOptions.UserLogin != "" { + // Resolve userLogin -> userID + if err := searchOptions.ResolveUserLogin(ctx, s.userSvc); err != nil { + return nil, err + } + } + if searchOptions.UserID == 0 { - return nil, fmt.Errorf("expected user ID to be specified") + return nil, fmt.Errorf("expected user ID or login to be specified") } if permissions, success := s.searchUserPermissionsFromCache(orgID, searchOptions); success { diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index ee9b0d4cebe..ff0649cc126 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/setting" ) @@ -27,12 +28,14 @@ func setupTestEnv(t testing.TB) *Service { cfg := setting.NewCfg() ac := &Service{ + cache: localcache.ProvideService(), cfg: cfg, + features: featuremgmt.WithFeatures(), log: log.New("accesscontrol"), registrations: accesscontrol.RegistrationList{}, - store: database.ProvideService(db.InitTestDB(t)), roles: accesscontrol.BuildBasicRoleDefinitions(), - features: featuremgmt.WithFeatures(), + store: database.ProvideService(db.InitTestDB(t)), + userSvc: usertest.NewUserServiceFake(), } require.NoError(t, ac.RegisterFixedRoles(context.Background())) return ac @@ -57,6 +60,7 @@ func TestUsageMetrics(t *testing.T) { cfg, database.ProvideService(db.InitTestDB(t)), localcache.ProvideService(), + usertest.NewUserServiceFake(), featuremgmt.WithFeatures(), ) assert.Equal(t, tt.expectedValue, s.GetUsageStats(context.Background())["stats.oss.accesscontrol.enabled.count"]) @@ -525,11 +529,77 @@ func TestService_SearchUsersPermissions(t *testing.T) { }, }, }, + { + // This test is not exactly representative as normally the store would return + // only the user's basic roles and the user's stored permissions + name: "check userID filter works correctly", + siuPermissions: listAllPerms, + searchOption: accesscontrol.SearchOptions{UserID: 1}, + ramRoles: map[string]*accesscontrol.RoleDTO{ + string(roletype.RoleEditor): {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, + }}, + string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"}, + }}, + accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}, + }}, + }, + storedPerms: map[int64][]accesscontrol.Permission{ + 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}}, + 2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}}, + }, + storedRoles: map[int64][]string{ + 1: {string(roletype.RoleEditor)}, + 2: {string(roletype.RoleAdmin), accesscontrol.RoleGrafanaAdmin}, + }, + want: map[int64][]accesscontrol.Permission{ + 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}, {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}}, + }, + }, + { + // This test is not exactly representative as normally the store would return + // only the user's basic roles and the user's stored permissions + name: "check userLogin filter works correctly", + siuPermissions: listAllPerms, + searchOption: accesscontrol.SearchOptions{UserLogin: "testUser"}, + ramRoles: map[string]*accesscontrol.RoleDTO{ + string(roletype.RoleEditor): {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, + }}, + string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"}, + }}, + accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}, + }}, + }, + storedPerms: map[int64][]accesscontrol.Permission{ + 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}}, + 2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}}, + }, + storedRoles: map[int64][]string{ + 1: {string(roletype.RoleEditor)}, + 2: {string(roletype.RoleAdmin), accesscontrol.RoleGrafanaAdmin}, + }, + want: map[int64][]accesscontrol.Permission{ + 2: {{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"}, + {Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ac := setupTestEnv(t) + // Resolve user login to id 2 + ac.userSvc = &usertest.FakeUserService{ExpectedUser: &user.User{ID: 2}} + ac.roles = tt.ramRoles ac.store = actest.FakeStore{ ExpectedUsersPermissions: tt.storedPerms, diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index 692cdc4df1f..0a8c9189ff2 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -69,17 +69,31 @@ func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) resp return response.JSON(http.StatusOK, ac.GroupScopesByAction(permissions)) } -// GET /api/access-control/users/permissions +// GET /api/access-control/users/permissions/search func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) response.Response { + userIDString := c.Query("userId") + userID, err := strconv.ParseInt(userIDString, 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "user ID is invalid", err) + } searchOptions := ac.SearchOptions{ + UserLogin: c.Query("userLogin"), ActionPrefix: c.Query("actionPrefix"), Action: c.Query("action"), Scope: c.Query("scope"), } + searchOptions.UserID = userID // Validate inputs - if (searchOptions.ActionPrefix != "") == (searchOptions.Action != "") { - return response.JSON(http.StatusBadRequest, "provide one of 'action' or 'actionPrefix'") + if (searchOptions.ActionPrefix != "") && (searchOptions.Action != "") { + return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive") + } + if (searchOptions.UserLogin != "") && (searchOptions.UserID > 0) { + return response.JSON(http.StatusBadRequest, "'userId' and 'userLogin' are mutually exclusive") + } + if searchOptions.UserID <= 0 && searchOptions.UserLogin == "" && + searchOptions.ActionPrefix == "" && searchOptions.Action == "" { + return response.JSON(http.StatusBadRequest, "at least one search option must be provided") } // Compute metadata @@ -101,7 +115,7 @@ func (api *AccessControlAPI) searchUserPermissions(c *contextmodel.ReqContext) r userIDString := web.Params(c.Req)[":userID"] userID, err := strconv.ParseInt(userIDString, 10, 64) if err != nil { - response.Error(http.StatusBadRequest, "user ID is invalid", err) + return response.Error(http.StatusBadRequest, "user ID is invalid", err) } searchOptions := ac.SearchOptions{ diff --git a/pkg/services/accesscontrol/api/api_test.go b/pkg/services/accesscontrol/api/api_test.go index 602602bab02..7cf1c3923bb 100644 --- a/pkg/services/accesscontrol/api/api_test.go +++ b/pkg/services/accesscontrol/api/api_test.go @@ -118,3 +118,77 @@ func TestAPI_getUserPermissions(t *testing.T) { }) } } + +func TestAccessControlAPI_searchUsersPermissions(t *testing.T) { + type testCase struct { + desc string + permissions map[int64][]ac.Permission + filters string + expectedOutput map[int64]map[string][]string + expectedCode int + } + + tests := []testCase{ + { + desc: "Should reject if no filter is provided", + expectedCode: http.StatusBadRequest, + }, + { + desc: "Should reject if conflicting action filters are provided", + filters: "?actionPrefix=grafana-test-app&action=grafana-test-app.projects:read", + expectedCode: http.StatusBadRequest, + }, + { + desc: "Should reject if conflicting user filters are provided", + filters: "?userLogin=admin&userId=2", + expectedCode: http.StatusBadRequest, + }, + { + desc: "Should reject if invalid userID is provided", + filters: "?userId=invalid", + expectedCode: http.StatusBadRequest, + }, + { + desc: "Should work with valid filter provided", + filters: "?userId=2", + permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:*"}}}, + expectedCode: http.StatusOK, + expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}}, + }, + { + desc: "Should reduce permissions", + filters: "?userId=2", + permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:id:1"}, {Action: "users:read", Scope: "users:*"}}}, + expectedCode: http.StatusOK, + expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}}, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + acSvc := actest.FakeService{ExpectedUsersPermissions: tt.permissions} + accessControl := actest.FakeAccessControl{ExpectedEvaluate: true} // Always allow access to the endpoint + api := NewAccessControlAPI(routing.NewRouteRegister(), accessControl, acSvc, featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)) + api.RegisterAPIEndpoints() + + server := webtest.NewServer(t, api.RouteRegister) + url := "/api/access-control/users/permissions/search" + tt.filters + + req := server.NewGetRequest(url) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{ + OrgID: 1, + Permissions: map[int64]map[string][]string{}, + }) + res, err := server.Send(req) + defer func() { require.NoError(t, res.Body.Close()) }() + require.NoError(t, err) + require.Equal(t, tt.expectedCode, res.StatusCode) + + if tt.expectedCode == http.StatusOK { + var output map[int64]map[string][]string + err := json.NewDecoder(res.Body).Decode(&output) + require.NoError(t, err) + require.Equal(t, tt.expectedOutput, output) + } + }) + } +} diff --git a/pkg/services/extsvcauth/oauthserver/oasimpl/service_test.go b/pkg/services/extsvcauth/oauthserver/oasimpl/service_test.go index 42a77a24e13..1f810a94669 100644 --- a/pkg/services/extsvcauth/oauthserver/oasimpl/service_test.go +++ b/pkg/services/extsvcauth/oauthserver/oasimpl/service_test.go @@ -83,7 +83,7 @@ func setupTestEnv(t *testing.T) *TestEnv { cache: localcache.New(cacheExpirationTime, cacheCleanupInterval), cfg: cfg, accessControl: acimpl.ProvideAccessControl(cfg), - acService: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), fmgt), + acService: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), env.UserService, fmgt), memstore: storage.NewMemoryStore(), sqlstore: env.OAuthStore, logger: log.New("oauthserver.test"), diff --git a/pkg/services/ngalert/migration/store/testing.go b/pkg/services/ngalert/migration/store/testing.go index f9345b5e104..022ce181086 100644 --- a/pkg/services/ngalert/migration/store/testing.go +++ b/pkg/services/ngalert/migration/store/testing.go @@ -54,8 +54,6 @@ func NewTestMigrationStore(t testing.TB, sqlStore *sqlstore.SQLStore, cfg *setti quotaService := "atest.FakeQuotaService{} ac := acimpl.ProvideAccessControl(cfg) routeRegister := routing.ProvideRegister() - acSvc, err := acimpl.ProvideService(cfg, sqlStore, routing.ProvideRegister(), cache, ac, features) - require.NoError(t, err) license := licensingtest.NewFakeLicensing() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() @@ -65,6 +63,9 @@ func NewTestMigrationStore(t testing.TB, sqlStore *sqlstore.SQLStore, cfg *setti userSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamSvc, cache, quotaService, bundleregistry.ProvideService()) require.NoError(t, err) + acSvc, err := acimpl.ProvideService(cfg, sqlStore, routing.ProvideRegister(), cache, ac, userSvc, features) + require.NoError(t, err) + dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, sqlStore, features, nil) diff --git a/pkg/services/serviceaccounts/extsvcaccounts/service_test.go b/pkg/services/serviceaccounts/extsvcaccounts/service_test.go index 1624c71ed82..db1e6521efd 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/service_test.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/service_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/services/secrets/kvstore" sa "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts/tests" + "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/setting" ) @@ -43,7 +44,7 @@ func setupTestEnv(t *testing.T) *TestEnv { } logger := log.New("extsvcaccounts.test") env.S = &ExtSvcAccountsService{ - acSvc: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), fmgt), + acSvc: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), usertest.NewUserServiceFake(), fmgt), features: fmgt, logger: logger, metrics: newMetrics(nil, env.SaSvc, logger), From 7230ec88a1cdf1ba759e212cc3d8418e59f734fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Fri, 26 Jan 2024 09:45:17 +0100 Subject: [PATCH 003/169] postgres: test: remove unused code (#81330) --- .../grafana-postgresql-datasource/postgres_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go b/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go index 54bb31e1f38..7a37b839150 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go +++ b/pkg/tsdb/grafana-postgresql-datasource/postgres_test.go @@ -2,7 +2,6 @@ package postgres import ( "context" - "database/sql" "fmt" "math/rand" "os" @@ -1371,17 +1370,6 @@ func TestIntegrationPostgres(t *testing.T) { }) } -func InitPostgresTestDB(t *testing.T, jsonData sqleng.JsonData) *sql.DB { - connStr := postgresTestDBConnString() - db, err := sql.Open("postgres", connStr) - require.NoError(t, err, "Failed to init postgres DB") - - db.SetMaxOpenConns(jsonData.MaxOpenConns) - db.SetMaxIdleConns(jsonData.MaxIdleConns) - db.SetConnMaxLifetime(time.Duration(jsonData.ConnMaxLifetime) * time.Second) - return db -} - func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time { durationSec := int64(duration.Seconds()) intervalSec := int64(interval.Seconds()) From 802012385d626f063a4bee4aac1672789a25455b Mon Sep 17 00:00:00 2001 From: Timur Olzhabayev Date: Fri, 26 Jan 2024 09:51:15 +0100 Subject: [PATCH 004/169] Chore: fixing PanelDataTransformationsTab Test (#81329) * fixing An update to Input inside a test was not wrapped in act(...). * no need to await twice * looks like we do need to await twice --- .../PanelDataTransformationsTab.test.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx index fd6af7a8aa0..f975f7256a8 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx @@ -102,8 +102,9 @@ describe('PanelDataTransformationsTab', () => { selectors.components.TransformTab.newTransform('Add field from calculation') ); const button = transformationCard.getElementsByTagName('button').item(0); - - await userEvent.click(button!); + await act(async () => { + await userEvent.click(button!); + }); expect(onChangeTransformation).toHaveBeenCalledWith([{ id: 'calculateField', options: {} }]); }); @@ -129,9 +130,9 @@ describe('PanelDataTransformationsTab', () => { selectors.components.TransformTab.newTransform('Add field from calculation') ); const button = transformationCard.getElementsByTagName('button').item(0); - - await userEvent.click(button!); - + await act(async () => { + await userEvent.click(button!); + }); expect(onChangeTransformation).toHaveBeenCalledWith([ { id: 'calculateField', options: {} }, { id: 'calculateField', options: {} }, From 19194ea122dc88abba7fc75a77dbf897789527d6 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Fri, 26 Jan 2024 10:11:41 +0100 Subject: [PATCH 005/169] RBAC: Remove redundant search endpoint (#81331) --- pkg/services/accesscontrol/api/api.go | 35 ++------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index 0a8c9189ff2..16bca4a6cc9 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -11,7 +11,6 @@ import ( ac "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/web" ) func NewAccessControlAPI(router routing.RouteRegister, accesscontrol ac.AccessControl, service ac.Service, @@ -38,9 +37,7 @@ func (api *AccessControlAPI) RegisterAPIEndpoints() { rr.Get("/user/actions", middleware.ReqSignedIn, routing.Wrap(api.getUserActions)) rr.Get("/user/permissions", middleware.ReqSignedIn, routing.Wrap(api.getUserPermissions)) if api.features.IsEnabledGlobally(featuremgmt.FlagAccessControlOnCall) { - userIDScope := ac.Scope("users", "id", ac.Parameter(":userID")) rr.Get("/users/permissions/search", authorize(ac.EvalPermission(ac.ActionUsersPermissionsRead)), routing.Wrap(api.searchUsersPermissions)) - rr.Get("/user/:userID/permissions/search", authorize(ac.EvalPermission(ac.ActionUsersPermissionsRead, userIDScope)), routing.Wrap(api.searchUserPermissions)) } }, requestmeta.SetOwner(requestmeta.TeamAuth)) } @@ -51,7 +48,7 @@ func (api *AccessControlAPI) getUserActions(c *contextmodel.ReqContext) response permissions, err := api.Service.GetUserPermissions(c.Req.Context(), c.SignedInUser, ac.Options{ReloadCache: reloadCache}) if err != nil { - response.JSON(http.StatusInternalServerError, err) + return response.JSON(http.StatusInternalServerError, err) } return response.JSON(http.StatusOK, ac.BuildPermissionsMap(permissions)) @@ -63,7 +60,7 @@ func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) resp permissions, err := api.Service.GetUserPermissions(c.Req.Context(), c.SignedInUser, ac.Options{ReloadCache: reloadCache}) if err != nil { - response.JSON(http.StatusInternalServerError, err) + return response.JSON(http.StatusInternalServerError, err) } return response.JSON(http.StatusOK, ac.GroupScopesByAction(permissions)) @@ -109,31 +106,3 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) return response.JSON(http.StatusOK, permsByAction) } - -// GET /api/access-control/user/:userID/permissions/search -func (api *AccessControlAPI) searchUserPermissions(c *contextmodel.ReqContext) response.Response { - userIDString := web.Params(c.Req)[":userID"] - userID, err := strconv.ParseInt(userIDString, 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "user ID is invalid", err) - } - - searchOptions := ac.SearchOptions{ - ActionPrefix: c.Query("actionPrefix"), - Action: c.Query("action"), - Scope: c.Query("scope"), - UserID: userID, - } - // Validate inputs - if (searchOptions.ActionPrefix != "") == (searchOptions.Action != "") { - return response.JSON(http.StatusBadRequest, "provide one of 'action' or 'actionPrefix'") - } - - permissions, err := api.Service.SearchUserPermissions(c.Req.Context(), - c.SignedInUser.GetOrgID(), searchOptions) - if err != nil { - response.Error(http.StatusInternalServerError, "could not search user permissions", err) - } - - return response.JSON(http.StatusOK, ac.Reduce(permissions)) -} From 8761dfcc9763644e0ec2b18567a3fa2ef63ad504 Mon Sep 17 00:00:00 2001 From: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:32:20 +0100 Subject: [PATCH 006/169] Add custom alert component (#81012) Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: Matias Chomicki --- .../src/TemporaryAlert.tsx | 51 +++++++ .../grafana-o11y-ds-frontend/src/index.ts | 1 + .../tempo/traceql/TraceQLEditor.tsx | 143 +++++++++--------- 3 files changed, 126 insertions(+), 69 deletions(-) create mode 100644 packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx diff --git a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx new file mode 100644 index 00000000000..3f7b6961e5b --- /dev/null +++ b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx @@ -0,0 +1,51 @@ +import { css } from '@emotion/css'; +import React, { useEffect, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, AlertVariant, useTheme2 } from '@grafana/ui'; + +enum AlertTimeout { + Error = 7000, + Info = 3000, + Success = 3000, + Warning = 5000, +} + +const getStyle = (theme: GrafanaTheme2) => { + return css({ + position: 'absolute', + zIndex: theme.zIndex.portal, + top: 0, + right: 10, + }); +}; + +const timeoutMap = { + ['error']: AlertTimeout.Error, + ['info']: AlertTimeout.Info, + ['success']: AlertTimeout.Success, + ['warning']: AlertTimeout.Warning, +}; + +export const TemporaryAlert = (props: { severity: AlertVariant; text: string }) => { + const style = getStyle(useTheme2()); + const [visible, setVisible] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + setVisible(false); + }, timeoutMap[props.severity]); + + return () => { + clearTimeout(timer); + }; + }, [props.severity, visible]); + + useEffect(() => { + if (props.text !== '') { + setVisible(true); + } + }, [props.text]); + + return ; +}; diff --git a/packages/grafana-o11y-ds-frontend/src/index.ts b/packages/grafana-o11y-ds-frontend/src/index.ts index 591a0cc5b5c..18bd6e95419 100644 --- a/packages/grafana-o11y-ds-frontend/src/index.ts +++ b/packages/grafana-o11y-ds-frontend/src/index.ts @@ -7,6 +7,7 @@ export * from './IntervalInput/IntervalInput'; export * from './NodeGraph/NodeGraphSettings'; export * from './SpanBar/SpanBarSettings'; +export * from './TemporaryAlert'; export * from './TraceToLogs/TagMappingInput'; export * from './TraceToLogs/TraceToLogsSettings'; export * from './TraceToMetrics/TraceToMetricsSettings'; diff --git a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx index 64aa15acfbd..5d5e001395c 100644 --- a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx +++ b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx @@ -1,13 +1,11 @@ import { css } from '@emotion/css'; -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { TemporaryAlert } from '@grafana/o11y-ds-frontend'; import { reportInteraction } from '@grafana/runtime'; import { CodeEditor, Monaco, monacoTypes, useTheme2 } from '@grafana/ui'; -import { notifyApp } from '../_importedDependencies/actions/appNotification'; -import { createErrorNotification } from '../_importedDependencies/core/appNotification'; -import { dispatch } from '../_importedDependencies/store'; import { TempoDatasource } from '../datasource'; import { CompletionProvider, CompletionType } from './autocomplete'; @@ -24,8 +22,10 @@ interface Props { } export function TraceQLEditor(props: Props) { + const [alertText, setAlertText] = useState('error error'); + const { onChange, onRunQuery, placeholder } = props; - const setupAutocompleteFn = useAutocomplete(props.datasource); + const setupAutocompleteFn = useAutocomplete(props.datasource, setAlertText); const theme = useTheme2(); const styles = getStyles(theme, placeholder); // work around the problem that `onEditorDidMount` is called once @@ -36,73 +36,76 @@ export function TraceQLEditor(props: Props) { const errorTimeoutId = useRef(); return ( - { - if (!props.readOnly) { - setupAutocompleteFn(editor, monaco, setupRegisterInteractionCommand(editor)); - setupActions(editor, monaco, () => onRunQueryRef.current()); - setupPlaceholder(editor, monaco, styles); - } - setupAutoSize(editor); + <> + { + if (!props.readOnly) { + setupAutocompleteFn(editor, monaco, setupRegisterInteractionCommand(editor)); + setupActions(editor, monaco, () => onRunQueryRef.current()); + setupPlaceholder(editor, monaco, styles); + } + setupAutoSize(editor); - // Parse query that might already exist (e.g., after a page refresh) - const model = editor.getModel(); - if (model) { - const errorNodes = getErrorNodes(model.getValue()); - setMarkers(monaco, model, errorNodes); - } - - // Register callback for query changes - editor.onDidChangeModelContent((changeEvent) => { + // Parse query that might already exist (e.g., after a page refresh) const model = editor.getModel(); - - if (!model) { - return; + if (model) { + const errorNodes = getErrorNodes(model.getValue()); + setMarkers(monaco, model, errorNodes); } - // Remove previous callback if existing, to prevent squiggles from been shown while the user is still typing - window.clearTimeout(errorTimeoutId.current); + // Register callback for query changes + editor.onDidChangeModelContent((changeEvent) => { + const model = editor.getModel(); - const errorNodes = getErrorNodes(model.getValue()); - const cursorPosition = changeEvent.changes[0].rangeOffset; + if (!model) { + return; + } - // Immediately updates the squiggles, in case the user fixed an error, - // excluding the error around the cursor position - setMarkers( - monaco, - model, - errorNodes.filter((errorNode) => !(errorNode.from <= cursorPosition && cursorPosition <= errorNode.to)) - ); + // Remove previous callback if existing, to prevent squiggles from been shown while the user is still typing + window.clearTimeout(errorTimeoutId.current); - // Later on, show all errors - errorTimeoutId.current = window.setTimeout(() => { - setMarkers(monaco, model, errorNodes); - }, 500); - }); - }} - /> + const errorNodes = getErrorNodes(model.getValue()); + const cursorPosition = changeEvent.changes[0].rangeOffset; + + // Immediately updates the squiggles, in case the user fixed an error, + // excluding the error around the cursor position + setMarkers( + monaco, + model, + errorNodes.filter((errorNode) => !(errorNode.from <= cursorPosition && cursorPosition <= errorNode.to)) + ); + + // Later on, show all errors + errorTimeoutId.current = window.setTimeout(() => { + setMarkers(monaco, model, errorNodes); + }, 500); + }); + }} + /> + {alertText && } + ); } @@ -175,9 +178,10 @@ function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) { /** * Hook that returns function that will set up monaco autocomplete for the label selector - * @param datasource + * @param datasource the Tempo datasource instance + * @param setAlertText setter for alert's text */ -function useAutocomplete(datasource: TempoDatasource) { +function useAutocomplete(datasource: TempoDatasource, setAlertText: (text: string) => void) { // We need the provider ref so we can pass it the label/values data later. This is because we run the call for the // values here but there is additional setup needed for the provider later on. We could run the getSeries() in the // returned function but that is run after the monaco is mounted so would delay the request a bit when it does not @@ -192,12 +196,13 @@ function useAutocomplete(datasource: TempoDatasource) { await datasource.languageProvider.start(); } catch (error) { if (error instanceof Error) { - dispatch(notifyApp(createErrorNotification('Error', error))); + console.error(error); + setAlertText(error.message); } } }; fetchTags(); - }, [datasource]); + }, [datasource, setAlertText]); const autocompleteDisposeFun = useRef<(() => void) | null>(null); useEffect(() => { From d86eac6ce08b79ba3d8b52dbcac55c0f5020a7bb Mon Sep 17 00:00:00 2001 From: Aaron Godin Date: Fri, 26 Jan 2024 03:44:11 -0600 Subject: [PATCH 007/169] fix: Upgrade ts-node to work with latest typescript (#81322) fix: plugins-bundled/internal/input-datasource build regression from ts-node version --- .../internal/input-datasource/package.json | 2 +- yarn.lock | 40 +------------------ 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/plugins-bundled/internal/input-datasource/package.json b/plugins-bundled/internal/input-datasource/package.json index c2c10a40013..a46c28fbeab 100644 --- a/plugins-bundled/internal/input-datasource/package.json +++ b/plugins-bundled/internal/input-datasource/package.json @@ -24,7 +24,7 @@ "jest-environment-jsdom": "29.3.1", "swc-loader": "0.2.3", "ts-jest": "29.0.5", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "webpack": "5.76.0" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index d89b2c53a3e..6c31a189dfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3062,7 +3062,7 @@ __metadata: react: "npm:18.2.0" swc-loader: "npm:0.2.3" ts-jest: "npm:29.0.5" - ts-node: "npm:10.9.1" + ts-node: "npm:10.9.2" tslib: "npm:2.5.0" webpack: "npm:5.76.0" languageName: unknown @@ -28918,44 +28918,6 @@ __metadata: languageName: node linkType: hard -"ts-node@npm:10.9.1": - version: 10.9.1 - resolution: "ts-node@npm:10.9.1" - dependencies: - "@cspotcode/source-map-support": "npm:^0.8.0" - "@tsconfig/node10": "npm:^1.0.7" - "@tsconfig/node12": "npm:^1.0.7" - "@tsconfig/node14": "npm:^1.0.0" - "@tsconfig/node16": "npm:^1.0.2" - acorn: "npm:^8.4.1" - acorn-walk: "npm:^8.1.1" - arg: "npm:^4.1.0" - create-require: "npm:^1.1.0" - diff: "npm:^4.0.1" - make-error: "npm:^1.1.1" - v8-compile-cache-lib: "npm:^3.0.1" - yn: "npm:3.1.1" - peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" - peerDependenciesMeta: - "@swc/core": - optional: true - "@swc/wasm": - optional: true - bin: - ts-node: dist/bin.js - ts-node-cwd: dist/bin-cwd.js - ts-node-esm: dist/bin-esm.js - ts-node-script: dist/bin-script.js - ts-node-transpile-only: dist/bin-transpile.js - ts-script: dist/bin-script-deprecated.js - checksum: bee56d4dc96ccbafc99dfab7b73fbabc62abab2562af53cdea91c874a301b9d11e42bc33c0a032a6ed6d813dbdc9295ec73dde7b73ea4ebde02b0e22006f7e04 - languageName: node - linkType: hard - "ts-node@npm:10.9.2, ts-node@npm:^10.2.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2" From b4d490883ead230407400eb3453e972f6febc118 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:47:09 +0000 Subject: [PATCH 008/169] Update dependency yaml to v2.3.4 (#81292) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6c31a189dfd..02207ab6726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30767,9 +30767,9 @@ __metadata: linkType: hard "yaml@npm:^2.0.0": - version: 2.3.1 - resolution: "yaml@npm:2.3.1" - checksum: 66501d597e43766eb94dc175d28ec8b2c63087d6a78783e59b4218eee32b9172740f9f27d54b7bc0ca8af61422f7134929f9974faeaac99d583787e793852fd2 + version: 2.3.4 + resolution: "yaml@npm:2.3.4" + checksum: f8207ce43065a22268a2806ea6a0fa3974c6fde92b4b2fa0082357e487bc333e85dc518910007e7ac001b532c7c84bd3eccb6c7757e94182b564028b0008f44b languageName: node linkType: hard From c886a5804dbc420163b5bfc2f56456ce2ed149f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:02:53 +0200 Subject: [PATCH 009/169] Update dependency eslint-plugin-import to v2.29.1 (#81302) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 91 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/yarn.lock b/yarn.lock index 02207ab6726..c8c2cbd261c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10847,7 +10847,20 @@ __metadata: languageName: node linkType: hard -"array.prototype.flat@npm:^1.3.1": +"array.prototype.findlastindex@npm:^1.2.3": + version: 1.2.3 + resolution: "array.prototype.findlastindex@npm:1.2.3" + dependencies: + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + es-shim-unscopables: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.1" + checksum: 063cbab8eeac3aa01f3e980eecb9a8c5d87723032b49f7f814ecc6d75c33c03c17e3f43a458127a62e16303cab412f95d6ad9dc7e0ae6d9dc27a9bb76c24df7a + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": version: 1.3.2 resolution: "array.prototype.flat@npm:1.3.2" dependencies: @@ -15338,18 +15351,18 @@ __metadata: languageName: node linkType: hard -"eslint-import-resolver-node@npm:^0.3.7": - version: 0.3.7 - resolution: "eslint-import-resolver-node@npm:0.3.7" +"eslint-import-resolver-node@npm:^0.3.9": + version: 0.3.9 + resolution: "eslint-import-resolver-node@npm:0.3.9" dependencies: debug: "npm:^3.2.7" - is-core-module: "npm:^2.11.0" - resolve: "npm:^1.22.1" - checksum: 31c6dfbd3457d1e6170ac2326b7ba9c323ff1ea68e3fcc5309f234bd1cefed050ee9b35e458b5eaed91323ab0d29bb2eddb41a1720ba7ca09bbacb00a0339d64 + is-core-module: "npm:^2.13.0" + resolve: "npm:^1.22.4" + checksum: d52e08e1d96cf630957272e4f2644dcfb531e49dcfd1edd2e07e43369eb2ec7a7d4423d417beee613201206ff2efa4eb9a582b5825ee28802fc7c71fcd53ca83 languageName: node linkType: hard -"eslint-module-utils@npm:^2.7.4": +"eslint-module-utils@npm:^2.8.0": version: 2.8.0 resolution: "eslint-module-utils@npm:2.8.0" dependencies: @@ -15362,27 +15375,29 @@ __metadata: linkType: hard "eslint-plugin-import@npm:^2.26.0": - version: 2.27.5 - resolution: "eslint-plugin-import@npm:2.27.5" + version: 2.29.1 + resolution: "eslint-plugin-import@npm:2.29.1" dependencies: - array-includes: "npm:^3.1.6" - array.prototype.flat: "npm:^1.3.1" - array.prototype.flatmap: "npm:^1.3.1" + array-includes: "npm:^3.1.7" + array.prototype.findlastindex: "npm:^1.2.3" + array.prototype.flat: "npm:^1.3.2" + array.prototype.flatmap: "npm:^1.3.2" debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" - eslint-import-resolver-node: "npm:^0.3.7" - eslint-module-utils: "npm:^2.7.4" - has: "npm:^1.0.3" - is-core-module: "npm:^2.11.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-module-utils: "npm:^2.8.0" + hasown: "npm:^2.0.0" + is-core-module: "npm:^2.13.1" is-glob: "npm:^4.0.3" minimatch: "npm:^3.1.2" - object.values: "npm:^1.1.6" - resolve: "npm:^1.22.1" - semver: "npm:^6.3.0" - tsconfig-paths: "npm:^3.14.1" + object.fromentries: "npm:^2.0.7" + object.groupby: "npm:^1.0.1" + object.values: "npm:^1.1.7" + semver: "npm:^6.3.1" + tsconfig-paths: "npm:^3.15.0" peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: b8ab9521bd47acdad959309cbb5635069cebd0f1dfd14b5f6ad24f609dfda82c604b029c7366cafce1d359845300957ec246587cd5e4b237a0378118a9d3dfa7 + checksum: 5865f05c38552145423c535326ec9a7113ab2305c7614c8b896ff905cfabc859c8805cac21e979c9f6f742afa333e6f62f812eabf891a7e8f5f0b853a32593c1 languageName: node linkType: hard @@ -18790,7 +18805,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.11.0, is-core-module@npm:^2.13.0, is-core-module@npm:^2.5.0, is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.13.1, is-core-module@npm:^2.5.0, is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": version: 2.13.1 resolution: "is-core-module@npm:2.13.1" dependencies: @@ -20311,7 +20326,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^1.0.1": +"json5@npm:^1.0.2": version: 1.0.2 resolution: "json5@npm:1.0.2" dependencies: @@ -22750,6 +22765,18 @@ __metadata: languageName: node linkType: hard +"object.groupby@npm:^1.0.1": + version: 1.0.1 + resolution: "object.groupby@npm:1.0.1" + dependencies: + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + get-intrinsic: "npm:^1.2.1" + checksum: b7123d91403f95d63978513b23a6079c30f503311f64035fafc863c291c787f287b58df3b21ef002ce1d0b820958c9009dd5a8ab696e0eca325639d345e41524 + languageName: node + linkType: hard + "object.hasown@npm:^1.1.2": version: 1.1.2 resolution: "object.hasown@npm:1.1.2" @@ -22769,7 +22796,7 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.6": +"object.values@npm:^1.1.6, object.values@npm:^1.1.7": version: 1.1.7 resolution: "object.values@npm:1.1.7" dependencies: @@ -26308,7 +26335,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1": +"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -26334,7 +26361,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin": +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -28956,15 +28983,15 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths@npm:^3.14.1": - version: 3.14.1 - resolution: "tsconfig-paths@npm:3.14.1" +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" dependencies: "@types/json5": "npm:^0.0.29" - json5: "npm:^1.0.1" + json5: "npm:^1.0.2" minimist: "npm:^1.2.6" strip-bom: "npm:^3.0.0" - checksum: 51be8bd8f90e49d2f8b3f61f544557e631dd5cee35e247dd316be27d723c9e99de9ce59eb39395ca20f1e43aedfc1fef0272ba25acb0a0e0e9a38cffd692256d + checksum: 2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 languageName: node linkType: hard From cd443b24db77831e8908786ff23e58ef9fe94108 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:13:55 +0000 Subject: [PATCH 010/169] Update dependency @floating-ui/react to v0.26.7 (#81334) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 48 ++++++++++++++++---------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index d333efe29a3..08a69ecb7df 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,7 @@ "@emotion/css": "11.11.2", "@emotion/react": "11.11.3", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.26.6", + "@floating-ui/react": "0.26.7", "@glideapps/glide-data-grid": "^6.0.0", "@grafana-plugins/grafana-azure-monitor-datasource": "workspace:*", "@grafana-plugins/grafana-pyroscope-datasource": "workspace:*", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index e75c8af837e..c661b4674a1 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -49,7 +49,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@emotion/react": "11.11.3", - "@floating-ui/react": "0.26.6", + "@floating-ui/react": "0.26.7", "@grafana/data": "10.4.0-pre", "@grafana/e2e-selectors": "10.4.0-pre", "@grafana/faro-web-sdk": "^1.3.6", diff --git a/yarn.lock b/yarn.lock index c8c2cbd261c..e5c481e5f93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2807,52 +2807,52 @@ __metadata: languageName: node linkType: hard -"@floating-ui/core@npm:^1.5.3": - version: 1.5.3 - resolution: "@floating-ui/core@npm:1.5.3" +"@floating-ui/core@npm:^1.6.0": + version: 1.6.0 + resolution: "@floating-ui/core@npm:1.6.0" dependencies: - "@floating-ui/utils": "npm:^0.2.0" - checksum: 7d9feaca2565a2a71bf03d23cd292c03def63097d7fde7d62909cdb8ddb84664781f3922086bcf10443f3310cb92381a0ecf745b2774edb917fa74fe61015c56 + "@floating-ui/utils": "npm:^0.2.1" + checksum: d6a47cacde193cd8ccb4c268b91ccc4ca254dffaec6242b07fd9bcde526044cc976d27933a7917f9a671de0a0e27f8d358f46400677dbd0c8199de293e9746e1 languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.5.4": - version: 1.5.4 - resolution: "@floating-ui/dom@npm:1.5.4" +"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.6.0": + version: 1.6.0 + resolution: "@floating-ui/dom@npm:1.6.0" dependencies: - "@floating-ui/core": "npm:^1.5.3" - "@floating-ui/utils": "npm:^0.2.0" - checksum: 3ba02ba2b4227c1e18df6ccdd029a1c100058db2e76ca1dac60a593ec72b2d4d995fa5c2d1639a5c38adb17e12398fbfe4f6cf5fd45f2ee6170ed0cf64acea06 + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.1" + checksum: fc559385a27feddff31bdb9f4c8bc05e12bda5aac4e44e7b5850a2af3d2960bf58d72034ccc3fc6c1bf5e69a97d6b5958ee73a885b5e7e7594dc4fa2c22ba47d languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.6": - version: 2.0.6 - resolution: "@floating-ui/react-dom@npm:2.0.6" +"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.7": + version: 2.0.7 + resolution: "@floating-ui/react-dom@npm:2.0.7" dependencies: - "@floating-ui/dom": "npm:^1.5.4" + "@floating-ui/dom": "npm:^1.6.0" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 4a3f828370c1b14af0e4d24df18de7b2cfd6695c189532d5949daaa43cba13ce7f231c71783b47e8c54baefcc2542650d82a4a6cebcf3d9091673f2a53d3a2cb + checksum: 15eb9ec493781253077e7e26c65c56586a1c92c414879c55d779cba5479d46b8897de06d61c831d03d24c24894257817fed9a515a4f2dc955666df5156dd757b languageName: node linkType: hard -"@floating-ui/react@npm:0.26.6": - version: 0.26.6 - resolution: "@floating-ui/react@npm:0.26.6" +"@floating-ui/react@npm:0.26.7": + version: 0.26.7 + resolution: "@floating-ui/react@npm:0.26.7" dependencies: - "@floating-ui/react-dom": "npm:^2.0.6" + "@floating-ui/react-dom": "npm:^2.0.7" "@floating-ui/utils": "npm:^0.2.1" tabbable: "npm:^6.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 15085b58e527f9acf617c434f9d3591ee69f039b0ebfb92374a2efdfb67fe5d21c36e4ce70eba13b41cdcfd355618e5bb1465706045f3774e1ce4884bb0c37e1 + checksum: 85af7118b0dcd852cfe6e3299a5e467f9224fe240d95b8de7d754d1be4a103e27fbc889fbbc3bd6bee9c3b9799e54bc2640f60dd5aa5bbb4a355cb906512ec13 languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.1": +"@floating-ui/utils@npm:^0.2.1": version: 0.2.1 resolution: "@floating-ui/utils@npm:0.2.1" checksum: 33c9ab346e7b05c5a1e6a95bc902aafcfc2c9d513a147e2491468843bd5607531b06d0b9aa56aa491cbf22a6c2495c18ccfc4c0344baec54a689a7bb8e4898d6 @@ -3650,7 +3650,7 @@ __metadata: "@babel/core": "npm:7.23.9" "@emotion/css": "npm:11.11.2" "@emotion/react": "npm:11.11.3" - "@floating-ui/react": "npm:0.26.6" + "@floating-ui/react": "npm:0.26.7" "@grafana/data": "npm:10.4.0-pre" "@grafana/e2e-selectors": "npm:10.4.0-pre" "@grafana/faro-web-sdk": "npm:^1.3.6" @@ -17277,7 +17277,7 @@ __metadata: "@emotion/eslint-plugin": "npm:11.11.0" "@emotion/react": "npm:11.11.3" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.26.6" + "@floating-ui/react": "npm:0.26.7" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana-plugins/grafana-azure-monitor-datasource": "workspace:*" "@grafana-plugins/grafana-pyroscope-datasource": "workspace:*" From 7512b1a519458d159a34946ff1783fdbaf1693e5 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Fri, 26 Jan 2024 11:23:48 +0100 Subject: [PATCH 011/169] RBAC: Search fix userID filter (#81337) --- pkg/services/accesscontrol/api/api.go | 15 +++++++++------ pkg/services/accesscontrol/api/api_test.go | 13 +++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index 16bca4a6cc9..c5f276ec8ac 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -68,18 +68,21 @@ func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) resp // GET /api/access-control/users/permissions/search func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) response.Response { - userIDString := c.Query("userId") - userID, err := strconv.ParseInt(userIDString, 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "user ID is invalid", err) - } searchOptions := ac.SearchOptions{ UserLogin: c.Query("userLogin"), ActionPrefix: c.Query("actionPrefix"), Action: c.Query("action"), Scope: c.Query("scope"), } - searchOptions.UserID = userID + + userIDString := c.Query("userId") + if userIDString != "" { + userID, err := strconv.ParseInt(userIDString, 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "user ID is invalid", err) + } + searchOptions.UserID = userID + } // Validate inputs if (searchOptions.ActionPrefix != "") && (searchOptions.Action != "") { diff --git a/pkg/services/accesscontrol/api/api_test.go b/pkg/services/accesscontrol/api/api_test.go index 7cf1c3923bb..85ec4620b1d 100644 --- a/pkg/services/accesscontrol/api/api_test.go +++ b/pkg/services/accesscontrol/api/api_test.go @@ -162,6 +162,19 @@ func TestAccessControlAPI_searchUsersPermissions(t *testing.T) { expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}}, }, + { + desc: "Should work with valid action filter", + filters: "?actionPrefix=users:", + permissions: map[int64][]ac.Permission{ + 1: {{Action: "users:write", Scope: "users:id:1"}}, + 2: {{Action: "users:read", Scope: "users:id:2"}}, + }, + expectedCode: http.StatusOK, + expectedOutput: map[int64]map[string][]string{ + 1: {"users:write": {"users:id:1"}}, + 2: {"users:read": {"users:id:2"}}, + }, + }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { From 2febbec758f73471938d9d5e68c15e2978667721 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:06:16 +0000 Subject: [PATCH 012/169] Update dependency @types/node to v20.11.7 --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 22 +++++++++---------- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 08a69ecb7df..775cf87d9ac 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "@types/lucene": "^2", "@types/marked": "5.0.2", "@types/mousetrap": "1.6.15", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "@types/node-forge": "^1", "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.4", "@types/papaparse": "5.3.14", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 367d5926028..50a9402a6b2 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -76,7 +76,7 @@ "@types/jquery": "3.5.29", "@types/lodash": "4.14.202", "@types/marked": "5.0.2", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "@types/papaparse": "5.3.14", "@types/react": "18.2.48", "@types/react-dom": "18.2.18", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 2fb837e57db..ebf0d74d412 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "25.0.7", "@rollup/plugin-node-resolve": "15.2.3", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "esbuild": "0.18.12", "rimraf": "5.0.5", "rollup": "2.79.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index c661b4674a1..b043aa83745 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -140,7 +140,7 @@ "@types/jquery": "3.5.29", "@types/lodash": "4.14.202", "@types/mock-raf": "1.0.6", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "@types/prismjs": "1.26.3", "@types/react": "18.2.48", "@types/react-beautiful-dnd": "13.1.8", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 1fd26812a4a..a98dd308b1e 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -29,7 +29,7 @@ "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.11", "@types/lodash": "4.14.202", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "@types/prismjs": "1.26.3", "@types/react": "18.2.48", "@types/testing-library__jest-dom": "5.14.9", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 67e7e505eef..7c6b327516b 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -25,7 +25,7 @@ "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.11", "@types/lodash": "4.14.202", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "@types/react": "18.2.48", "@types/testing-library__jest-dom": "5.14.9", "@types/uuid": "9.0.7", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 6c17f379c93..534d359d9ee 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -47,7 +47,7 @@ "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.11", "@types/lodash": "4.14.202", - "@types/node": "20.11.6", + "@types/node": "20.11.7", "@types/prismjs": "1.26.3", "@types/react": "18.2.48", "@types/react-dom": "18.2.18", diff --git a/yarn.lock b/yarn.lock index e5c481e5f93..22db725ce72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2949,7 +2949,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.11" "@types/lodash": "npm:4.14.202" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" "@types/prismjs": "npm:1.26.3" "@types/react": "npm:18.2.48" "@types/testing-library__jest-dom": "npm:5.14.9" @@ -3027,7 +3027,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.11" "@types/lodash": "npm:4.14.202" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" "@types/react": "npm:18.2.48" "@types/testing-library__jest-dom": "npm:5.14.9" "@types/uuid": "npm:9.0.7" @@ -3122,7 +3122,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.11" "@types/lodash": "npm:4.14.202" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" "@types/prismjs": "npm:1.26.3" "@types/react": "npm:18.2.48" "@types/react-dom": "npm:18.2.18" @@ -3195,7 +3195,7 @@ __metadata: "@types/jquery": "npm:3.5.29" "@types/lodash": "npm:4.14.202" "@types/marked": "npm:5.0.2" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" "@types/papaparse": "npm:5.3.14" "@types/react": "npm:18.2.48" "@types/react-dom": "npm:18.2.18" @@ -3257,7 +3257,7 @@ __metadata: "@grafana/tsconfig": "npm:^1.2.0-rc1" "@rollup/plugin-commonjs": "npm:25.0.7" "@rollup/plugin-node-resolve": "npm:15.2.3" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" esbuild: "npm:0.18.12" rimraf: "npm:5.0.5" rollup: "npm:2.79.1" @@ -3691,7 +3691,7 @@ __metadata: "@types/jquery": "npm:3.5.29" "@types/lodash": "npm:4.14.202" "@types/mock-raf": "npm:1.0.6" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" "@types/prismjs": "npm:1.26.3" "@types/react": "npm:18.2.48" "@types/react-beautiful-dnd": "npm:13.1.8" @@ -8849,12 +8849,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:20.11.6, @types/node@npm:>=13.7.0": - version: 20.11.6 - resolution: "@types/node@npm:20.11.6" +"@types/node@npm:*, @types/node@npm:20.11.7, @types/node@npm:>=13.7.0": + version: 20.11.7 + resolution: "@types/node@npm:20.11.7" dependencies: undici-types: "npm:~5.26.4" - checksum: 375dfc75f0aa8b7fb99382d84c317e5dab2969dcc3f54d8490b119151060923eae6483839552347bc78262bbcefb2951ee131e15372f4e80b755be2dda9afe80 + checksum: ff0428c093987f1f6e3400b91450077cf006d7f0e4ff316e20636218562c53761565ceb06bd7a161badf46e6884d78b554c7b1c3a7717cf5cb8c97f461ecd754 languageName: node linkType: hard @@ -17361,7 +17361,7 @@ __metadata: "@types/lucene": "npm:^2" "@types/marked": "npm:5.0.2" "@types/mousetrap": "npm:1.6.15" - "@types/node": "npm:20.11.6" + "@types/node": "npm:20.11.7" "@types/node-forge": "npm:^1" "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.4" "@types/papaparse": "npm:5.3.14" From 29e8a355cbca4f554f14161abe0b2de0cd5b8a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Fri, 26 Jan 2024 11:38:29 +0100 Subject: [PATCH 013/169] sql: extract frontend code into separate package (#81109) * sql: extract frontend code into separate package * updated package version --- .betterer.results | 44 +++++++-------- .github/CODEOWNERS | 2 +- package.json | 2 +- packages/grafana-sql/package.json | 54 +++++++++++++++++++ .../grafana-sql/src}/ResponseParser.test.ts | 0 .../grafana-sql/src}/ResponseParser.ts | 0 .../src}/components/ConfirmModal.tsx | 0 .../src}/components/DatasetSelector.tsx | 0 .../src}/components/ErrorBoundary.tsx | 0 .../src}/components/QueryEditor.tsx | 0 .../QueryEditorFeatureFlag.utils.ts | 0 .../src}/components/QueryHeader.tsx | 0 .../src}/components/SqlComponents.test.tsx | 0 .../components/SqlComponents.testHelpers.ts | 0 .../src}/components/TableSelector.tsx | 0 .../configuration/ConnectionLimits.tsx | 0 .../src}/components/configuration/Divider.tsx | 0 .../configuration/TLSSecretsConfig.tsx | 0 .../useMigrateDatabaseFields.test.ts | 0 .../configuration/useMigrateDatabaseFields.ts | 0 .../grafana-sql/src}/components/index.ts | 0 .../query-editor-raw/QueryEditorRaw.tsx | 0 .../query-editor-raw/QueryToolbox.tsx | 0 .../query-editor-raw/QueryValidator.tsx | 0 .../components/query-editor-raw/README.md | 0 .../components/query-editor-raw/RawEditor.tsx | 0 .../AwesomeQueryBuilder.tsx | 0 .../visual-query-builder/GroupByRow.tsx | 0 .../visual-query-builder/OrderByRow.tsx | 0 .../visual-query-builder/Preview.tsx | 0 .../visual-query-builder/SQLGroupByRow.tsx | 0 .../visual-query-builder/SQLOrderByRow.tsx | 0 .../visual-query-builder/SQLSelectRow.tsx | 0 .../visual-query-builder/SQLWhereRow.tsx | 0 .../visual-query-builder/SelectRow.tsx | 0 .../visual-query-builder/VisualEditor.tsx | 0 .../visual-query-builder/WhereRow.tsx | 0 .../components/visual-query-builder/index.ts | 0 .../grafana-sql/src}/constants.ts | 0 .../src}/datasource/SqlDatasource.ts | 0 .../grafana-sql/src}/defaults.ts | 0 .../grafana-sql/src}/expressions.ts | 0 packages/grafana-sql/src/index.ts | 22 ++++++++ .../sql => packages/grafana-sql/src}/types.ts | 0 .../grafana-sql/src}/utils/formatSQL.ts | 0 .../grafana-sql/src}/utils/migration.test.ts | 0 .../grafana-sql/src}/utils/migration.ts | 0 .../grafana-sql/src}/utils/sql.utils.ts | 0 .../grafana-sql/src}/utils/testHelpers.ts | 0 .../grafana-sql/src}/utils/useSqlChange.ts | 0 packages/grafana-sql/tsconfig.json | 13 +++++ public/app/features/plugins/sql/.eslintrc | 10 ---- public/app/features/plugins/sql/index.ts | 2 - .../PostgresQueryEditor.tsx | 5 +- .../PostgresQueryModel.ts | 3 +- .../configuration/ConfigurationEditor.tsx | 5 +- .../datasource.test.ts | 3 +- .../datasource.ts | 4 +- .../grafana-postgresql-datasource/module.ts | 2 +- .../sqlCompletionProvider.ts | 2 +- .../grafana-postgresql-datasource/sqlUtil.ts | 3 +- .../grafana-postgresql-datasource/types.ts | 2 +- .../editor/query/fsql/FSQLEditor.tsx | 4 +- .../plugins/datasource/influxdb/datasource.ts | 3 +- .../influxdb/datasource_sql.test.ts | 3 +- .../influxdb/fsql/datasource.flightsql.ts | 4 +- .../datasource/influxdb/fsql/fields.ts | 2 +- .../datasource/influxdb/fsql/sqlUtil.test.ts | 4 +- .../datasource/influxdb/fsql/sqlUtil.ts | 3 +- .../plugins/datasource/influxdb/fsql/types.ts | 2 +- .../app/plugins/datasource/influxdb/mocks.ts | 2 +- .../datasource/mssql/MSSqlQueryModel.ts | 3 +- .../configuration/ConfigurationEditor.tsx | 3 +- .../datasource/mssql/datasource.test.ts | 2 +- .../plugins/datasource/mssql/datasource.ts | 4 +- public/app/plugins/datasource/mssql/module.ts | 3 +- .../datasource/mssql/sqlCompletionProvider.ts | 2 +- .../app/plugins/datasource/mssql/sqlUtil.ts | 3 +- public/app/plugins/datasource/mssql/types.ts | 2 +- .../datasource/mysql/MySqlDatasource.ts | 4 +- .../configuration/ConfigurationEditor.tsx | 5 +- public/app/plugins/datasource/mysql/fields.ts | 2 +- public/app/plugins/datasource/mysql/module.ts | 3 +- .../datasource/mysql/specs/datasource.test.ts | 3 +- .../app/plugins/datasource/mysql/sqlUtil.ts | 3 +- public/app/plugins/datasource/mysql/types.ts | 2 +- yarn.lock | 42 ++++++++++++++- 87 files changed, 187 insertions(+), 104 deletions(-) create mode 100644 packages/grafana-sql/package.json rename {public/app/features/plugins/sql => packages/grafana-sql/src}/ResponseParser.test.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/ResponseParser.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/ConfirmModal.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/DatasetSelector.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/ErrorBoundary.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/QueryEditor.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/QueryEditorFeatureFlag.utils.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/QueryHeader.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/SqlComponents.test.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/SqlComponents.testHelpers.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/TableSelector.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/configuration/ConnectionLimits.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/configuration/Divider.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/configuration/TLSSecretsConfig.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/configuration/useMigrateDatabaseFields.test.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/configuration/useMigrateDatabaseFields.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/index.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/query-editor-raw/QueryEditorRaw.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/query-editor-raw/QueryToolbox.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/query-editor-raw/QueryValidator.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/query-editor-raw/README.md (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/query-editor-raw/RawEditor.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/AwesomeQueryBuilder.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/GroupByRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/OrderByRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/Preview.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/SQLGroupByRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/SQLOrderByRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/SQLSelectRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/SQLWhereRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/SelectRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/VisualEditor.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/WhereRow.tsx (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/components/visual-query-builder/index.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/constants.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/datasource/SqlDatasource.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/defaults.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/expressions.ts (100%) create mode 100644 packages/grafana-sql/src/index.ts rename {public/app/features/plugins/sql => packages/grafana-sql/src}/types.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/utils/formatSQL.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/utils/migration.test.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/utils/migration.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/utils/sql.utils.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/utils/testHelpers.ts (100%) rename {public/app/features/plugins/sql => packages/grafana-sql/src}/utils/useSqlChange.ts (100%) create mode 100644 packages/grafana-sql/tsconfig.json delete mode 100644 public/app/features/plugins/sql/.eslintrc delete mode 100644 public/app/features/plugins/sql/index.ts diff --git a/.betterer.results b/.betterer.results index 1795f47d98b..06eeb130f0e 100644 --- a/.betterer.results +++ b/.betterer.results @@ -597,6 +597,28 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "5"], [0, 0, 0, "Do not use any type assertions.", "6"] ], + "packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx:5381": [ + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"], + [0, 0, 0, "Styles should be written using objects.", "2"], + [0, 0, 0, "Styles should be written using objects.", "3"], + [0, 0, 0, "Styles should be written using objects.", "4"] + ], + "packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx:5381": [ + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"], + [0, 0, 0, "Styles should be written using objects.", "2"] + ], + "packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx:5381": [ + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"] + ], + "packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + ], + "packages/grafana-sql/src/components/visual-query-builder/SQLWhereRow.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3943,28 +3965,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"] ], - "public/app/features/plugins/sql/components/query-editor-raw/QueryToolbox.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"] - ], - "public/app/features/plugins/sql/components/query-editor-raw/QueryValidator.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/features/plugins/sql/components/query-editor-raw/RawEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], - "public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/features/plugins/sql/components/visual-query-builder/SQLWhereRow.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/plugins/tests/datasource_srv.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1ec930b449d..02a56c82a58 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -341,6 +341,7 @@ /plugins-bundled/ @grafana/plugins-platform-frontend /packages/grafana-plugin-configs/ @grafana/plugins-platform-frontend /packages/grafana-o11y-ds-frontend/ @grafana/observability-logs @grafana/observability-traces-and-profiling +/packages/grafana-sql/ @grafana/partner-datasources @grafana/oss-big-tent # root files, mostly frontend .browserslistrc @grafana/frontend-ops @@ -409,7 +410,6 @@ cypress.config.js @grafana/grafana-frontend-platform /public/app/features/panel/ @grafana/dashboards-squad /public/app/features/playlist/ @grafana/dashboards-squad /public/app/features/plugins/ @grafana/plugins-platform-frontend -/public/app/features/plugins/sql/ @grafana/partner-datasources @grafana/oss-big-tent /public/app/features/profile/ @grafana/grafana-frontend-platform /public/app/features/runtime/ @ryantxu /public/app/features/query/ @grafana/dashboards-squad diff --git a/package.json b/package.json index 775cf87d9ac..4dd90bbe8b8 100644 --- a/package.json +++ b/package.json @@ -245,6 +245,7 @@ "@grafana/runtime": "workspace:*", "@grafana/scenes": "^2.0.0", "@grafana/schema": "workspace:*", + "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "^7.4.0", "@leeoniya/ufuzzy": "1.0.14", @@ -390,7 +391,6 @@ "slate": "0.47.9", "slate-plain-serializer": "0.7.13", "slate-react": "0.22.10", - "sql-formatter-plus": "^1.3.6", "symbol-observable": "4.0.0", "test": "link:./public/test", "tether-drop": "https://github.com/torkelo/drop", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json new file mode 100644 index 00000000000..2aab491d35e --- /dev/null +++ b/packages/grafana-sql/package.json @@ -0,0 +1,54 @@ +{ + "author": "Grafana Labs", + "license": "AGPL-3.0-only", + "private": true, + "name": "@grafana/sql", + "version": "10.4.0-pre", + "repository": { + "type": "git", + "url": "http://github.com/grafana/grafana.git", + "directory": "packages/grafana-sql" + }, + "main": "src/index.ts", + "scripts": { + "typecheck": "tsc --emitDeclarationOnly false --noEmit" + }, + "dependencies": { + "@emotion/css": "11.11.2", + "@grafana/data": "workspace:*", + "@grafana/experimental": "1.7.7", + "@grafana/runtime": "workspace:*", + "@grafana/ui": "workspace:*", + "@react-awesome-query-builder/ui": "6.4.2", + "@types/lodash": "4.14.202", + "@types/react-virtualized-auto-sizer": "1.0.4", + "@types/uuid": "9.0.7", + "immutable": "4.3.4", + "lodash": "4.17.21", + "react-use": "17.4.0", + "react-virtualized-auto-sizer": "1.0.21", + "rxjs": "7.8.1", + "sql-formatter-plus": "^1.3.6", + "tslib": "2.6.2", + "uuid": "9.0.1" + }, + "devDependencies": { + "@grafana/tsconfig": "^1.3.0-rc1", + "@testing-library/jest-dom": "^6.1.2", + "@testing-library/react": "14.1.2", + "@testing-library/react-hooks": "^8.0.1", + "@testing-library/user-event": "14.5.2", + "@types/jest": "^29.5.4", + "@types/react": "18.2.48", + "@types/systemjs": "6.13.5", + "@types/testing-library__jest-dom": "5.14.9", + "jest": "^29.6.4", + "react": "18.2.0", + "ts-jest": "29.1.2", + "ts-node": "10.9.2", + "typescript": "5.2.2" + }, + "peerDependencies": { + "@grafana/runtime": "workspace:*" + } +} diff --git a/public/app/features/plugins/sql/ResponseParser.test.ts b/packages/grafana-sql/src/ResponseParser.test.ts similarity index 100% rename from public/app/features/plugins/sql/ResponseParser.test.ts rename to packages/grafana-sql/src/ResponseParser.test.ts diff --git a/public/app/features/plugins/sql/ResponseParser.ts b/packages/grafana-sql/src/ResponseParser.ts similarity index 100% rename from public/app/features/plugins/sql/ResponseParser.ts rename to packages/grafana-sql/src/ResponseParser.ts diff --git a/public/app/features/plugins/sql/components/ConfirmModal.tsx b/packages/grafana-sql/src/components/ConfirmModal.tsx similarity index 100% rename from public/app/features/plugins/sql/components/ConfirmModal.tsx rename to packages/grafana-sql/src/components/ConfirmModal.tsx diff --git a/public/app/features/plugins/sql/components/DatasetSelector.tsx b/packages/grafana-sql/src/components/DatasetSelector.tsx similarity index 100% rename from public/app/features/plugins/sql/components/DatasetSelector.tsx rename to packages/grafana-sql/src/components/DatasetSelector.tsx diff --git a/public/app/features/plugins/sql/components/ErrorBoundary.tsx b/packages/grafana-sql/src/components/ErrorBoundary.tsx similarity index 100% rename from public/app/features/plugins/sql/components/ErrorBoundary.tsx rename to packages/grafana-sql/src/components/ErrorBoundary.tsx diff --git a/public/app/features/plugins/sql/components/QueryEditor.tsx b/packages/grafana-sql/src/components/QueryEditor.tsx similarity index 100% rename from public/app/features/plugins/sql/components/QueryEditor.tsx rename to packages/grafana-sql/src/components/QueryEditor.tsx diff --git a/public/app/features/plugins/sql/components/QueryEditorFeatureFlag.utils.ts b/packages/grafana-sql/src/components/QueryEditorFeatureFlag.utils.ts similarity index 100% rename from public/app/features/plugins/sql/components/QueryEditorFeatureFlag.utils.ts rename to packages/grafana-sql/src/components/QueryEditorFeatureFlag.utils.ts diff --git a/public/app/features/plugins/sql/components/QueryHeader.tsx b/packages/grafana-sql/src/components/QueryHeader.tsx similarity index 100% rename from public/app/features/plugins/sql/components/QueryHeader.tsx rename to packages/grafana-sql/src/components/QueryHeader.tsx diff --git a/public/app/features/plugins/sql/components/SqlComponents.test.tsx b/packages/grafana-sql/src/components/SqlComponents.test.tsx similarity index 100% rename from public/app/features/plugins/sql/components/SqlComponents.test.tsx rename to packages/grafana-sql/src/components/SqlComponents.test.tsx diff --git a/public/app/features/plugins/sql/components/SqlComponents.testHelpers.ts b/packages/grafana-sql/src/components/SqlComponents.testHelpers.ts similarity index 100% rename from public/app/features/plugins/sql/components/SqlComponents.testHelpers.ts rename to packages/grafana-sql/src/components/SqlComponents.testHelpers.ts diff --git a/public/app/features/plugins/sql/components/TableSelector.tsx b/packages/grafana-sql/src/components/TableSelector.tsx similarity index 100% rename from public/app/features/plugins/sql/components/TableSelector.tsx rename to packages/grafana-sql/src/components/TableSelector.tsx diff --git a/public/app/features/plugins/sql/components/configuration/ConnectionLimits.tsx b/packages/grafana-sql/src/components/configuration/ConnectionLimits.tsx similarity index 100% rename from public/app/features/plugins/sql/components/configuration/ConnectionLimits.tsx rename to packages/grafana-sql/src/components/configuration/ConnectionLimits.tsx diff --git a/public/app/features/plugins/sql/components/configuration/Divider.tsx b/packages/grafana-sql/src/components/configuration/Divider.tsx similarity index 100% rename from public/app/features/plugins/sql/components/configuration/Divider.tsx rename to packages/grafana-sql/src/components/configuration/Divider.tsx diff --git a/public/app/features/plugins/sql/components/configuration/TLSSecretsConfig.tsx b/packages/grafana-sql/src/components/configuration/TLSSecretsConfig.tsx similarity index 100% rename from public/app/features/plugins/sql/components/configuration/TLSSecretsConfig.tsx rename to packages/grafana-sql/src/components/configuration/TLSSecretsConfig.tsx diff --git a/public/app/features/plugins/sql/components/configuration/useMigrateDatabaseFields.test.ts b/packages/grafana-sql/src/components/configuration/useMigrateDatabaseFields.test.ts similarity index 100% rename from public/app/features/plugins/sql/components/configuration/useMigrateDatabaseFields.test.ts rename to packages/grafana-sql/src/components/configuration/useMigrateDatabaseFields.test.ts diff --git a/public/app/features/plugins/sql/components/configuration/useMigrateDatabaseFields.ts b/packages/grafana-sql/src/components/configuration/useMigrateDatabaseFields.ts similarity index 100% rename from public/app/features/plugins/sql/components/configuration/useMigrateDatabaseFields.ts rename to packages/grafana-sql/src/components/configuration/useMigrateDatabaseFields.ts diff --git a/public/app/features/plugins/sql/components/index.ts b/packages/grafana-sql/src/components/index.ts similarity index 100% rename from public/app/features/plugins/sql/components/index.ts rename to packages/grafana-sql/src/components/index.ts diff --git a/public/app/features/plugins/sql/components/query-editor-raw/QueryEditorRaw.tsx b/packages/grafana-sql/src/components/query-editor-raw/QueryEditorRaw.tsx similarity index 100% rename from public/app/features/plugins/sql/components/query-editor-raw/QueryEditorRaw.tsx rename to packages/grafana-sql/src/components/query-editor-raw/QueryEditorRaw.tsx diff --git a/public/app/features/plugins/sql/components/query-editor-raw/QueryToolbox.tsx b/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx similarity index 100% rename from public/app/features/plugins/sql/components/query-editor-raw/QueryToolbox.tsx rename to packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx diff --git a/public/app/features/plugins/sql/components/query-editor-raw/QueryValidator.tsx b/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx similarity index 100% rename from public/app/features/plugins/sql/components/query-editor-raw/QueryValidator.tsx rename to packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx diff --git a/public/app/features/plugins/sql/components/query-editor-raw/README.md b/packages/grafana-sql/src/components/query-editor-raw/README.md similarity index 100% rename from public/app/features/plugins/sql/components/query-editor-raw/README.md rename to packages/grafana-sql/src/components/query-editor-raw/README.md diff --git a/public/app/features/plugins/sql/components/query-editor-raw/RawEditor.tsx b/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx similarity index 100% rename from public/app/features/plugins/sql/components/query-editor-raw/RawEditor.tsx rename to packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx b/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx rename to packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/GroupByRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/GroupByRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/GroupByRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/GroupByRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/OrderByRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/OrderByRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/OrderByRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/OrderByRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/Preview.tsx b/packages/grafana-sql/src/components/visual-query-builder/Preview.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/Preview.tsx rename to packages/grafana-sql/src/components/visual-query-builder/Preview.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/SQLGroupByRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SQLGroupByRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/SQLGroupByRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/SQLGroupByRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/SQLOrderByRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SQLOrderByRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/SQLOrderByRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/SQLOrderByRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/SQLSelectRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SQLSelectRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/SQLSelectRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/SQLSelectRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/SQLWhereRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SQLWhereRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/SQLWhereRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/SQLWhereRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/SelectRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SelectRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/SelectRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/SelectRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/VisualEditor.tsx b/packages/grafana-sql/src/components/visual-query-builder/VisualEditor.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/VisualEditor.tsx rename to packages/grafana-sql/src/components/visual-query-builder/VisualEditor.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/WhereRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/WhereRow.tsx similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/WhereRow.tsx rename to packages/grafana-sql/src/components/visual-query-builder/WhereRow.tsx diff --git a/public/app/features/plugins/sql/components/visual-query-builder/index.ts b/packages/grafana-sql/src/components/visual-query-builder/index.ts similarity index 100% rename from public/app/features/plugins/sql/components/visual-query-builder/index.ts rename to packages/grafana-sql/src/components/visual-query-builder/index.ts diff --git a/public/app/features/plugins/sql/constants.ts b/packages/grafana-sql/src/constants.ts similarity index 100% rename from public/app/features/plugins/sql/constants.ts rename to packages/grafana-sql/src/constants.ts diff --git a/public/app/features/plugins/sql/datasource/SqlDatasource.ts b/packages/grafana-sql/src/datasource/SqlDatasource.ts similarity index 100% rename from public/app/features/plugins/sql/datasource/SqlDatasource.ts rename to packages/grafana-sql/src/datasource/SqlDatasource.ts diff --git a/public/app/features/plugins/sql/defaults.ts b/packages/grafana-sql/src/defaults.ts similarity index 100% rename from public/app/features/plugins/sql/defaults.ts rename to packages/grafana-sql/src/defaults.ts diff --git a/public/app/features/plugins/sql/expressions.ts b/packages/grafana-sql/src/expressions.ts similarity index 100% rename from public/app/features/plugins/sql/expressions.ts rename to packages/grafana-sql/src/expressions.ts diff --git a/packages/grafana-sql/src/index.ts b/packages/grafana-sql/src/index.ts new file mode 100644 index 00000000000..a88463c3775 --- /dev/null +++ b/packages/grafana-sql/src/index.ts @@ -0,0 +1,22 @@ +export type { + DB, + RAQBFieldTypes, + SQLExpression, + SQLOptions, + SQLQuery, + SqlQueryModel, + SQLSelectableValue, +} from './types'; +export { QueryFormat } from './types'; // this is an enum, we cannot export-type it +export { SqlDatasource } from './datasource/SqlDatasource'; +export { formatSQL } from './utils/formatSQL'; +export { ConnectionLimits } from './components/configuration/ConnectionLimits'; +export { Divider } from './components/configuration/Divider'; +export { TLSSecretsConfig } from './components/configuration/TLSSecretsConfig'; +export { useMigrateDatabaseFields } from './components/configuration/useMigrateDatabaseFields'; +export { SqlQueryEditor } from './components/QueryEditor'; +export type { QueryHeaderProps } from './components/QueryHeader'; +export { createSelectClause, haveColumns } from './utils/sql.utils'; +export { applyQueryDefaults } from './defaults'; +export { makeVariable } from './utils/testHelpers'; +export { QueryEditorExpressionType } from './expressions'; diff --git a/public/app/features/plugins/sql/types.ts b/packages/grafana-sql/src/types.ts similarity index 100% rename from public/app/features/plugins/sql/types.ts rename to packages/grafana-sql/src/types.ts diff --git a/public/app/features/plugins/sql/utils/formatSQL.ts b/packages/grafana-sql/src/utils/formatSQL.ts similarity index 100% rename from public/app/features/plugins/sql/utils/formatSQL.ts rename to packages/grafana-sql/src/utils/formatSQL.ts diff --git a/public/app/features/plugins/sql/utils/migration.test.ts b/packages/grafana-sql/src/utils/migration.test.ts similarity index 100% rename from public/app/features/plugins/sql/utils/migration.test.ts rename to packages/grafana-sql/src/utils/migration.test.ts diff --git a/public/app/features/plugins/sql/utils/migration.ts b/packages/grafana-sql/src/utils/migration.ts similarity index 100% rename from public/app/features/plugins/sql/utils/migration.ts rename to packages/grafana-sql/src/utils/migration.ts diff --git a/public/app/features/plugins/sql/utils/sql.utils.ts b/packages/grafana-sql/src/utils/sql.utils.ts similarity index 100% rename from public/app/features/plugins/sql/utils/sql.utils.ts rename to packages/grafana-sql/src/utils/sql.utils.ts diff --git a/public/app/features/plugins/sql/utils/testHelpers.ts b/packages/grafana-sql/src/utils/testHelpers.ts similarity index 100% rename from public/app/features/plugins/sql/utils/testHelpers.ts rename to packages/grafana-sql/src/utils/testHelpers.ts diff --git a/public/app/features/plugins/sql/utils/useSqlChange.ts b/packages/grafana-sql/src/utils/useSqlChange.ts similarity index 100% rename from public/app/features/plugins/sql/utils/useSqlChange.ts rename to packages/grafana-sql/src/utils/useSqlChange.ts diff --git a/packages/grafana-sql/tsconfig.json b/packages/grafana-sql/tsconfig.json new file mode 100644 index 00000000000..a2f6548df35 --- /dev/null +++ b/packages/grafana-sql/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "declarationDir": "./compiled", + "emitDeclarationOnly": true, + "isolatedModules": true, + "strict": true, + "rootDirs": ["."] + }, + "exclude": ["dist/**/*"], + "extends": "@grafana/tsconfig", + "include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"] +} diff --git a/public/app/features/plugins/sql/.eslintrc b/public/app/features/plugins/sql/.eslintrc deleted file mode 100644 index f0bce9b7b63..00000000000 --- a/public/app/features/plugins/sql/.eslintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": ["app/*"] - } - ] - } -} diff --git a/public/app/features/plugins/sql/index.ts b/public/app/features/plugins/sql/index.ts deleted file mode 100644 index 195d95f598d..00000000000 --- a/public/app/features/plugins/sql/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './components'; -export * from './types'; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryEditor.tsx b/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryEditor.tsx index 24f173893ee..f1fa982ae93 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryEditor.tsx @@ -1,10 +1,7 @@ import React from 'react'; import { QueryEditorProps } from '@grafana/data'; -import { SqlQueryEditor } from 'app/features/plugins/sql/components/QueryEditor'; -import { SQLOptions, SQLQuery } from 'app/features/plugins/sql/types'; - -import { QueryHeaderProps } from '../../../features/plugins/sql/components/QueryHeader'; +import { SqlQueryEditor, SQLOptions, SQLQuery, QueryHeaderProps } from '@grafana/sql'; import { PostgresDatasource } from './datasource'; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryModel.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryModel.ts index aa3c47f0a78..a572733f1ee 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryModel.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/PostgresQueryModel.ts @@ -1,8 +1,7 @@ import { ScopedVars } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; import { VariableFormatID } from '@grafana/schema'; -import { applyQueryDefaults } from 'app/features/plugins/sql/defaults'; -import { SQLQuery, SqlQueryModel } from 'app/features/plugins/sql/types'; +import { SQLQuery, SqlQueryModel, applyQueryDefaults } from '@grafana/sql'; export class PostgresQueryModel implements SqlQueryModel { target: SQLQuery; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx index 7253d9f02cd..29ced54320c 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx @@ -10,6 +10,7 @@ import { } from '@grafana/data'; import { ConfigSection, ConfigSubSection, DataSourceDescription, Stack } from '@grafana/experimental'; import { config } from '@grafana/runtime'; +import { ConnectionLimits, Divider, TLSSecretsConfig, useMigrateDatabaseFields } from '@grafana/sql'; import { Input, Select, @@ -22,10 +23,6 @@ import { SecureSocksProxySettings, Collapse, } from '@grafana/ui'; -import { ConnectionLimits } from 'app/features/plugins/sql/components/configuration/ConnectionLimits'; -import { Divider } from 'app/features/plugins/sql/components/configuration/Divider'; -import { TLSSecretsConfig } from 'app/features/plugins/sql/components/configuration/TLSSecretsConfig'; -import { useMigrateDatabaseFields } from 'app/features/plugins/sql/components/configuration/useMigrateDatabaseFields'; import { PostgresOptions, PostgresTLSMethods, PostgresTLSModes, SecureJsonData } from '../types'; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.test.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.test.ts index f5379945829..0ef5b7e95e3 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.test.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.test.ts @@ -21,8 +21,7 @@ import { getDataSourceSrv, setDataSourceSrv, } from '@grafana/runtime'; -import { QueryFormat, SQLQuery } from 'app/features/plugins/sql/types'; -import { makeVariable } from 'app/features/plugins/sql/utils/testHelpers'; +import { QueryFormat, SQLQuery, makeVariable } from '@grafana/sql'; import { PostgresDatasource } from './datasource'; import { PostgresOptions } from './types'; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.ts index eca17ba04c5..cbaa49a8c46 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/datasource.ts @@ -1,9 +1,7 @@ import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { LanguageDefinition } from '@grafana/experimental'; import { TemplateSrv } from '@grafana/runtime'; -import { SqlDatasource } from 'app/features/plugins/sql/datasource/SqlDatasource'; -import { DB, SQLQuery, SQLSelectableValue } from 'app/features/plugins/sql/types'; -import { formatSQL } from 'app/features/plugins/sql/utils/formatSQL'; +import { SqlDatasource, DB, SQLQuery, SQLSelectableValue, formatSQL } from '@grafana/sql'; import { PostgresQueryModel } from './PostgresQueryModel'; import { getSchema, getTimescaleDBVersion, getVersion, showTables } from './postgresMetaQuery'; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/module.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/module.ts index 6aa5667cfd0..e03aa2e4d0d 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/module.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/module.ts @@ -1,5 +1,5 @@ import { DataSourcePlugin } from '@grafana/data'; -import { SQLQuery } from 'app/features/plugins/sql/types'; +import { SQLQuery } from '@grafana/sql'; import { CheatSheet } from './CheatSheet'; import { PostgresQueryEditor } from './PostgresQueryEditor'; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/sqlCompletionProvider.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/sqlCompletionProvider.ts index 485e972582d..9c1c4f92205 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/sqlCompletionProvider.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/sqlCompletionProvider.ts @@ -5,7 +5,7 @@ import { TableDefinition, TableIdentifier, } from '@grafana/experimental'; -import { DB, SQLQuery } from 'app/features/plugins/sql/types'; +import { DB, SQLQuery } from '@grafana/sql'; interface CompletionProviderGetterArgs { getColumns: React.MutableRefObject<(t: SQLQuery) => Promise>; diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/sqlUtil.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/sqlUtil.ts index 3c652fb2719..75e77f54882 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/sqlUtil.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/sqlUtil.ts @@ -1,7 +1,6 @@ import { isEmpty } from 'lodash'; -import { RAQBFieldTypes, SQLQuery } from 'app/features/plugins/sql/types'; -import { createSelectClause, haveColumns } from 'app/features/plugins/sql/utils/sql.utils'; +import { createSelectClause, haveColumns, RAQBFieldTypes, SQLQuery } from '@grafana/sql'; export function getFieldConfig(type: string): { raqbFieldType: RAQBFieldTypes; icon: string } { switch (type) { diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/types.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/types.ts index bc744f6c9a6..a10a4354ad0 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/types.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/types.ts @@ -1,4 +1,4 @@ -import { SQLOptions } from 'app/features/plugins/sql/types'; +import { SQLOptions } from '@grafana/sql'; export enum PostgresTLSModes { disable = 'disable', diff --git a/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx b/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx index ab16fdccbb0..90c08ff26ab 100644 --- a/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx @@ -2,11 +2,9 @@ import { css, cx } from '@emotion/css'; import React, { PureComponent } from 'react'; import { GrafanaTheme2 } from '@grafana/data/src'; +import { SQLQuery, SqlQueryEditor, applyQueryDefaults } from '@grafana/sql'; import { InlineFormLabel, LinkButton, Themeable2, withTheme2 } from '@grafana/ui/src'; -import { SQLQuery } from '../../../../../../../features/plugins/sql'; -import { SqlQueryEditor } from '../../../../../../../features/plugins/sql/components/QueryEditor'; -import { applyQueryDefaults } from '../../../../../../../features/plugins/sql/defaults'; import InfluxDatasource from '../../../../datasource'; import { FlightSQLDatasource } from '../../../../fsql/datasource.flightsql'; import { InfluxQuery } from '../../../../types'; diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 3b80766e82c..a7ce73bebed 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -32,11 +32,10 @@ import { getBackendSrv, } from '@grafana/runtime'; import { CustomFormatterVariable } from '@grafana/scenes'; +import { QueryFormat, SQLQuery } from '@grafana/sql'; import config from 'app/core/config'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -import { QueryFormat, SQLQuery } from '../../../features/plugins/sql'; - import { AnnotationEditor } from './components/editor/annotation/AnnotationEditor'; import { FluxQueryEditor } from './components/editor/query/flux/FluxQueryEditor'; import { BROWSER_MODE_DISABLED_MESSAGE } from './constants'; diff --git a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts index fa0af1ed35d..705b370a264 100644 --- a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts +++ b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts @@ -1,9 +1,8 @@ import { lastValueFrom } from 'rxjs'; +import { SQLQuery } from '@grafana/sql'; import config from 'app/core/config'; -import { SQLQuery } from '../../../features/plugins/sql'; - import InfluxDatasource from './datasource'; import { getMockDSInstanceSettings, diff --git a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts index ee9ee95c28e..476df864d0a 100644 --- a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts +++ b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts @@ -1,9 +1,7 @@ import { DataSourceInstanceSettings, TimeRange } from '@grafana/data'; import { CompletionItemKind, LanguageDefinition, TableIdentifier } from '@grafana/experimental'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; -import { SqlDatasource } from 'app/features/plugins/sql/datasource/SqlDatasource'; -import { DB, SQLQuery } from 'app/features/plugins/sql/types'; -import { formatSQL } from 'app/features/plugins/sql/utils/formatSQL'; +import { DB, SqlDatasource, SQLQuery, formatSQL } from '@grafana/sql'; import { mapFieldsToTypes } from './fields'; import { buildColumnQuery, buildTableQuery } from './flightsqlMetaQuery'; diff --git a/public/app/plugins/datasource/influxdb/fsql/fields.ts b/public/app/plugins/datasource/influxdb/fsql/fields.ts index 6177234a3db..1cc2609f676 100644 --- a/public/app/plugins/datasource/influxdb/fsql/fields.ts +++ b/public/app/plugins/datasource/influxdb/fsql/fields.ts @@ -1,4 +1,4 @@ -import { RAQBFieldTypes, SQLSelectableValue } from 'app/features/plugins/sql/types'; +import { RAQBFieldTypes, SQLSelectableValue } from '@grafana/sql'; export function mapFieldsToTypes(columns: SQLSelectableValue[]) { const fields: SQLSelectableValue[] = []; diff --git a/public/app/plugins/datasource/influxdb/fsql/sqlUtil.test.ts b/public/app/plugins/datasource/influxdb/fsql/sqlUtil.test.ts index d05aad13d4d..57fe15c6709 100644 --- a/public/app/plugins/datasource/influxdb/fsql/sqlUtil.test.ts +++ b/public/app/plugins/datasource/influxdb/fsql/sqlUtil.test.ts @@ -1,6 +1,4 @@ -import { SQLQuery } from 'app/features/plugins/sql/types'; - -import { QueryEditorExpressionType } from '../../../../features/plugins/sql/expressions'; +import { SQLQuery, QueryEditorExpressionType } from '@grafana/sql'; import { toRawSql } from './sqlUtil'; diff --git a/public/app/plugins/datasource/influxdb/fsql/sqlUtil.ts b/public/app/plugins/datasource/influxdb/fsql/sqlUtil.ts index f4b8eea30bb..81ad2673762 100644 --- a/public/app/plugins/datasource/influxdb/fsql/sqlUtil.ts +++ b/public/app/plugins/datasource/influxdb/fsql/sqlUtil.ts @@ -1,7 +1,6 @@ import { isEmpty } from 'lodash'; -import { SQLQuery } from 'app/features/plugins/sql/types'; -import { createSelectClause, haveColumns } from 'app/features/plugins/sql/utils/sql.utils'; +import { SQLQuery, createSelectClause, haveColumns } from '@grafana/sql'; // remove identifier quoting from identifier to use in metadata queries export function unquoteIdentifier(value: string) { diff --git a/public/app/plugins/datasource/influxdb/fsql/types.ts b/public/app/plugins/datasource/influxdb/fsql/types.ts index 805fa085eb3..787ed135bb0 100644 --- a/public/app/plugins/datasource/influxdb/fsql/types.ts +++ b/public/app/plugins/datasource/influxdb/fsql/types.ts @@ -1,4 +1,4 @@ -import { SQLOptions, SQLQuery } from 'app/features/plugins/sql/types'; +import { SQLOptions, SQLQuery } from '@grafana/sql'; export interface FlightSQLOptions extends SQLOptions { allowCleartextPasswords?: boolean; diff --git a/public/app/plugins/datasource/influxdb/mocks.ts b/public/app/plugins/datasource/influxdb/mocks.ts index 22b7b8be019..678c4ea283d 100644 --- a/public/app/plugins/datasource/influxdb/mocks.ts +++ b/public/app/plugins/datasource/influxdb/mocks.ts @@ -16,8 +16,8 @@ import { setBackendSrv, VariableInterpolation, } from '@grafana/runtime/src'; +import { SQLQuery } from '@grafana/sql'; -import { SQLQuery } from '../../../features/plugins/sql'; import { TemplateSrv } from '../../../features/templating/template_srv'; import InfluxDatasource from './datasource'; diff --git a/public/app/plugins/datasource/mssql/MSSqlQueryModel.ts b/public/app/plugins/datasource/mssql/MSSqlQueryModel.ts index 45754ed51c9..4d712428679 100644 --- a/public/app/plugins/datasource/mssql/MSSqlQueryModel.ts +++ b/public/app/plugins/datasource/mssql/MSSqlQueryModel.ts @@ -1,7 +1,6 @@ import { ScopedVars } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; -import { applyQueryDefaults } from 'app/features/plugins/sql/defaults'; -import { SQLQuery, SqlQueryModel } from 'app/features/plugins/sql/types'; +import { applyQueryDefaults, SQLQuery, SqlQueryModel } from '@grafana/sql'; export class MSSqlQueryModel implements SqlQueryModel { target: SQLQuery; diff --git a/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx index 19e1e8a42c8..b949fcbc4ca 100644 --- a/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx +++ b/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx @@ -11,6 +11,7 @@ import { updateDatasourcePluginResetOption, } from '@grafana/data'; import { ConfigSection, ConfigSubSection, DataSourceDescription } from '@grafana/experimental'; +import { ConnectionLimits, useMigrateDatabaseFields } from '@grafana/sql'; import { Alert, FieldSet, @@ -26,8 +27,6 @@ import { } from '@grafana/ui'; import { NumberInput } from 'app/core/components/OptionsUI/NumberInput'; import { config } from 'app/core/config'; -import { ConnectionLimits } from 'app/features/plugins/sql/components/configuration/ConnectionLimits'; -import { useMigrateDatabaseFields } from 'app/features/plugins/sql/components/configuration/useMigrateDatabaseFields'; import { AzureAuthSettings } from '../azureauth/AzureAuthSettings'; import { diff --git a/public/app/plugins/datasource/mssql/datasource.test.ts b/public/app/plugins/datasource/mssql/datasource.test.ts index bff446b08c1..8c3d8092d43 100644 --- a/public/app/plugins/datasource/mssql/datasource.test.ts +++ b/public/app/plugins/datasource/mssql/datasource.test.ts @@ -11,8 +11,8 @@ import { createDataFrame, TimeRange, } from '@grafana/data'; +import { SQLQuery } from '@grafana/sql'; import { backendSrv } from 'app/core/services/backend_srv'; -import { SQLQuery } from 'app/features/plugins/sql/types'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts index 13e6781f0bf..f936ab72a5a 100644 --- a/public/app/plugins/datasource/mssql/datasource.ts +++ b/public/app/plugins/datasource/mssql/datasource.ts @@ -1,9 +1,7 @@ import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { LanguageDefinition } from '@grafana/experimental'; import { TemplateSrv } from '@grafana/runtime'; -import { SqlDatasource } from 'app/features/plugins/sql/datasource/SqlDatasource'; -import { DB, SQLQuery, SQLSelectableValue } from 'app/features/plugins/sql/types'; -import { formatSQL } from 'app/features/plugins/sql/utils/formatSQL'; +import { DB, SQLQuery, SqlDatasource, SQLSelectableValue, formatSQL } from '@grafana/sql'; import { getSchema, showDatabases, getSchemaAndName } from './MSSqlMetaQuery'; import { MSSqlQueryModel } from './MSSqlQueryModel'; diff --git a/public/app/plugins/datasource/mssql/module.ts b/public/app/plugins/datasource/mssql/module.ts index 7af8a5a3f2f..230f2f89a41 100644 --- a/public/app/plugins/datasource/mssql/module.ts +++ b/public/app/plugins/datasource/mssql/module.ts @@ -1,6 +1,5 @@ import { DataSourcePlugin } from '@grafana/data'; -import { SqlQueryEditor } from 'app/features/plugins/sql/components/QueryEditor'; -import { SQLQuery } from 'app/features/plugins/sql/types'; +import { SQLQuery, SqlQueryEditor } from '@grafana/sql'; import { CheatSheet } from './CheatSheet'; import { ConfigurationEditor } from './configuration/ConfigurationEditor'; diff --git a/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts b/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts index e8fcfb4f25b..871c2172dfc 100644 --- a/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts +++ b/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts @@ -7,7 +7,7 @@ import { TableIdentifier, TokenType, } from '@grafana/experimental'; -import { DB, SQLQuery } from 'app/features/plugins/sql/types'; +import { DB, SQLQuery } from '@grafana/sql'; interface CompletionProviderGetterArgs { getColumns: React.MutableRefObject<(t: SQLQuery) => Promise>; diff --git a/public/app/plugins/datasource/mssql/sqlUtil.ts b/public/app/plugins/datasource/mssql/sqlUtil.ts index 49c631d5733..ba7347e77ae 100644 --- a/public/app/plugins/datasource/mssql/sqlUtil.ts +++ b/public/app/plugins/datasource/mssql/sqlUtil.ts @@ -1,7 +1,6 @@ import { isEmpty } from 'lodash'; -import { RAQBFieldTypes, SQLExpression, SQLQuery } from 'app/features/plugins/sql/types'; -import { haveColumns } from 'app/features/plugins/sql/utils/sql.utils'; +import { RAQBFieldTypes, SQLExpression, SQLQuery, haveColumns } from '@grafana/sql'; export function getIcon(type: string): string | undefined { switch (type) { diff --git a/public/app/plugins/datasource/mssql/types.ts b/public/app/plugins/datasource/mssql/types.ts index d950e4bef09..40c1914b34b 100644 --- a/public/app/plugins/datasource/mssql/types.ts +++ b/public/app/plugins/datasource/mssql/types.ts @@ -1,6 +1,6 @@ import { DataSourceJsonData } from '@grafana/data'; +import { SQLOptions } from '@grafana/sql'; import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types'; -import { SQLOptions } from 'app/features/plugins/sql/types'; export enum MSSQLAuthenticationType { sqlAuth = 'SQL Server Authentication', diff --git a/public/app/plugins/datasource/mysql/MySqlDatasource.ts b/public/app/plugins/datasource/mysql/MySqlDatasource.ts index 74bc814fb36..7a30358313c 100644 --- a/public/app/plugins/datasource/mysql/MySqlDatasource.ts +++ b/public/app/plugins/datasource/mysql/MySqlDatasource.ts @@ -1,8 +1,6 @@ import { DataSourceInstanceSettings, TimeRange } from '@grafana/data'; import { CompletionItemKind, LanguageDefinition, TableIdentifier } from '@grafana/experimental'; -import { SqlDatasource } from 'app/features/plugins/sql/datasource/SqlDatasource'; -import { DB, SQLQuery } from 'app/features/plugins/sql/types'; -import { formatSQL } from 'app/features/plugins/sql/utils/formatSQL'; +import { SqlDatasource, DB, SQLQuery, formatSQL } from '@grafana/sql'; import { mapFieldsToTypes } from './fields'; import { buildColumnQuery, buildTableQuery, showDatabases } from './mySqlMetaQuery'; diff --git a/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx index 6bd2640cec3..c0feed4629b 100644 --- a/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx +++ b/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx @@ -9,6 +9,7 @@ import { } from '@grafana/data'; import { ConfigSection, ConfigSubSection, DataSourceDescription, Stack } from '@grafana/experimental'; import { config } from '@grafana/runtime'; +import { ConnectionLimits, Divider, TLSSecretsConfig, useMigrateDatabaseFields } from '@grafana/sql'; import { Collapse, Field, @@ -20,10 +21,6 @@ import { Switch, Tooltip, } from '@grafana/ui'; -import { ConnectionLimits } from 'app/features/plugins/sql/components/configuration/ConnectionLimits'; -import { Divider } from 'app/features/plugins/sql/components/configuration/Divider'; -import { TLSSecretsConfig } from 'app/features/plugins/sql/components/configuration/TLSSecretsConfig'; -import { useMigrateDatabaseFields } from 'app/features/plugins/sql/components/configuration/useMigrateDatabaseFields'; import { MySQLOptions } from '../types'; diff --git a/public/app/plugins/datasource/mysql/fields.ts b/public/app/plugins/datasource/mysql/fields.ts index 96eb122c35e..4154da5873e 100644 --- a/public/app/plugins/datasource/mysql/fields.ts +++ b/public/app/plugins/datasource/mysql/fields.ts @@ -1,4 +1,4 @@ -import { RAQBFieldTypes, SQLSelectableValue } from 'app/features/plugins/sql/types'; +import { RAQBFieldTypes, SQLSelectableValue } from '@grafana/sql'; export function mapFieldsToTypes(columns: SQLSelectableValue[]) { const fields: SQLSelectableValue[] = []; diff --git a/public/app/plugins/datasource/mysql/module.ts b/public/app/plugins/datasource/mysql/module.ts index c47743252d6..c2c6616c492 100644 --- a/public/app/plugins/datasource/mysql/module.ts +++ b/public/app/plugins/datasource/mysql/module.ts @@ -1,6 +1,5 @@ import { DataSourcePlugin } from '@grafana/data'; -import { SqlQueryEditor } from 'app/features/plugins/sql/components/QueryEditor'; -import { SQLQuery } from 'app/features/plugins/sql/types'; +import { SQLQuery, SqlQueryEditor } from '@grafana/sql'; import { CheatSheet } from './CheatSheet'; import { MySqlDatasource } from './MySqlDatasource'; diff --git a/public/app/plugins/datasource/mysql/specs/datasource.test.ts b/public/app/plugins/datasource/mysql/specs/datasource.test.ts index aaee5eabf0b..1b9ced8b098 100644 --- a/public/app/plugins/datasource/mysql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mysql/specs/datasource.test.ts @@ -10,8 +10,7 @@ import { createDataFrame, } from '@grafana/data'; import { FetchResponse } from '@grafana/runtime'; -import { SQLQuery } from 'app/features/plugins/sql/types'; -import { makeVariable } from 'app/features/plugins/sql/utils/testHelpers'; +import { SQLQuery, makeVariable } from '@grafana/sql'; import { MySqlDatasource } from '../MySqlDatasource'; import { MySQLOptions } from '../types'; diff --git a/public/app/plugins/datasource/mysql/sqlUtil.ts b/public/app/plugins/datasource/mysql/sqlUtil.ts index 9116be17a93..c4e1587fddb 100644 --- a/public/app/plugins/datasource/mysql/sqlUtil.ts +++ b/public/app/plugins/datasource/mysql/sqlUtil.ts @@ -1,7 +1,6 @@ import { isEmpty } from 'lodash'; -import { SQLQuery } from 'app/features/plugins/sql/types'; -import { createSelectClause, haveColumns } from 'app/features/plugins/sql/utils/sql.utils'; +import { SQLQuery, createSelectClause, haveColumns } from '@grafana/sql'; export function toRawSql({ sql, dataset, table }: SQLQuery): string { let rawQuery = ''; diff --git a/public/app/plugins/datasource/mysql/types.ts b/public/app/plugins/datasource/mysql/types.ts index e142f35891a..cd47ae73ff2 100644 --- a/public/app/plugins/datasource/mysql/types.ts +++ b/public/app/plugins/datasource/mysql/types.ts @@ -1,4 +1,4 @@ -import { SQLOptions, SQLQuery } from 'app/features/plugins/sql/types'; +import { SQLOptions, SQLQuery } from '@grafana/sql'; export interface MySQLOptions extends SQLOptions { allowCleartextPasswords?: boolean; diff --git a/yarn.lock b/yarn.lock index 22db725ce72..9e83be69d4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3629,6 +3629,46 @@ __metadata: languageName: unknown linkType: soft +"@grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": + version: 0.0.0-use.local + resolution: "@grafana/sql@workspace:packages/grafana-sql" + dependencies: + "@emotion/css": "npm:11.11.2" + "@grafana/data": "workspace:*" + "@grafana/experimental": "npm:1.7.7" + "@grafana/runtime": "workspace:*" + "@grafana/tsconfig": "npm:^1.3.0-rc1" + "@grafana/ui": "workspace:*" + "@react-awesome-query-builder/ui": "npm:6.4.2" + "@testing-library/jest-dom": "npm:^6.1.2" + "@testing-library/react": "npm:14.1.2" + "@testing-library/react-hooks": "npm:^8.0.1" + "@testing-library/user-event": "npm:14.5.2" + "@types/jest": "npm:^29.5.4" + "@types/lodash": "npm:4.14.202" + "@types/react": "npm:18.2.48" + "@types/react-virtualized-auto-sizer": "npm:1.0.4" + "@types/systemjs": "npm:6.13.5" + "@types/testing-library__jest-dom": "npm:5.14.9" + "@types/uuid": "npm:9.0.7" + immutable: "npm:4.3.4" + jest: "npm:^29.6.4" + lodash: "npm:4.17.21" + react: "npm:18.2.0" + react-use: "npm:17.4.0" + react-virtualized-auto-sizer: "npm:1.0.21" + rxjs: "npm:7.8.1" + sql-formatter-plus: "npm:^1.3.6" + ts-jest: "npm:29.1.2" + ts-node: "npm:10.9.2" + tslib: "npm:2.6.2" + typescript: "npm:5.2.2" + uuid: "npm:9.0.1" + peerDependencies: + "@grafana/runtime": "workspace:*" + languageName: unknown + linkType: soft + "@grafana/tsconfig@npm:^1.2.0-rc1": version: 1.2.0-rc1 resolution: "@grafana/tsconfig@npm:1.2.0-rc1" @@ -17300,6 +17340,7 @@ __metadata: "@grafana/runtime": "workspace:*" "@grafana/scenes": "npm:^2.0.0" "@grafana/schema": "workspace:*" + "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" "@grafana/ui": "workspace:*" "@kusto/monaco-kusto": "npm:^7.4.0" @@ -17572,7 +17613,6 @@ __metadata: slate: "npm:0.47.9" slate-plain-serializer: "npm:0.7.13" slate-react: "npm:0.22.10" - sql-formatter-plus: "npm:^1.3.6" style-loader: "npm:3.3.4" stylelint: "npm:15.11.0" stylelint-config-prettier: "npm:9.0.5" From f3f36e37fa2fa33cd530f4213c41833f72bc5db4 Mon Sep 17 00:00:00 2001 From: Jo Date: Fri, 26 Jan 2024 11:54:00 +0100 Subject: [PATCH 014/169] AuthInfo: No mandatory auth_id in Auth Info service (#81335) * fix auth info update not having mandatory auth_id * remove uneeded newline --- .../generic-oauth/index.md | 1 + pkg/services/authn/clients/oauth.go | 5 ++ pkg/services/login/authinfoimpl/service.go | 65 ++++++++++--------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md index 780bf99099c..d86b1e434b2 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md @@ -41,6 +41,7 @@ To follow this guide: - Ensure that you have access to the [Grafana configuration file]({{< relref "../../../configure-grafana#configuration-file-location" >}}). - Ensure you know how to create an OAuth2 application with your OAuth2 provider. Consult the documentation of your OAuth2 provider for more information. +- Ensure your identity provider returns OpenID UserInfo compatible information such as `sub` claim. - If you are using refresh tokens, ensure you know how to set them up with your OAuth2 provider. Consult the documentation of your OAuth2 provider for more information. ## Steps diff --git a/pkg/services/authn/clients/oauth.go b/pkg/services/authn/clients/oauth.go index 0c6d7099e5a..96f530924e2 100644 --- a/pkg/services/authn/clients/oauth.go +++ b/pkg/services/authn/clients/oauth.go @@ -147,6 +147,11 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden return nil, errOAuthUserInfo.Errorf("failed to get user info: %w", err) } + // Implement in Grafana 11 + // if userInfo.Id == "" { + // return nil, errors.New("idP did not return a user id") + // } + if userInfo.Email == "" { return nil, errOAuthMissingRequiredEmail.Errorf("required attribute email was not provided") } diff --git a/pkg/services/login/authinfoimpl/service.go b/pkg/services/login/authinfoimpl/service.go index ef82d4a150b..4bd5814302b 100644 --- a/pkg/services/login/authinfoimpl/service.go +++ b/pkg/services/login/authinfoimpl/service.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/util/errutil" ) type Service struct { @@ -24,7 +25,7 @@ type Service struct { const remoteCachePrefix = "authinfo-" const remoteCacheTTL = 60 * time.Hour -var errMissingParameters = errors.New("user ID and auth ID must be set") +var errMissingParameters = errutil.NewBase(errutil.StatusBadRequest, "auth-missing-parameters", errutil.WithPublicMessage("Missing parameters for auth info")) func ProvideService(authInfoStore login.Store, remoteCache remotecache.CacheStorage, @@ -118,12 +119,12 @@ func generateCacheKey(query *login.GetAuthInfoQuery) string { } func (s *Service) UpdateAuthInfo(ctx context.Context, cmd *login.UpdateAuthInfoCommand) error { - if cmd.UserId == 0 || cmd.AuthId == "" { - return errMissingParameters + // Only update auth info if we have an (user id + auth module) + if cmd.UserId == 0 || cmd.AuthModule == "" { + return errMissingParameters.Errorf("missing parameters for auth info %v", cmd) } - err := s.authInfoStore.UpdateAuthInfo(ctx, cmd) - if err != nil { + if err := s.authInfoStore.UpdateAuthInfo(ctx, cmd); err != nil { return err } @@ -137,12 +138,12 @@ func (s *Service) UpdateAuthInfo(ctx context.Context, cmd *login.UpdateAuthInfoC } func (s *Service) SetAuthInfo(ctx context.Context, cmd *login.SetAuthInfoCommand) error { - if cmd.UserId == 0 || cmd.AuthId == "" { - return errMissingParameters + // Only set auth info if we have an (user id + auth module) + if cmd.UserId == 0 || cmd.AuthModule == "" { + return errMissingParameters.Errorf("missing parameters for auth info %v", cmd) } - err := s.authInfoStore.SetAuthInfo(ctx, cmd) - if err != nil { + if err := s.authInfoStore.SetAuthInfo(ctx, cmd); err != nil { return err } @@ -172,28 +173,32 @@ func (s *Service) DeleteUserAuthInfo(ctx context.Context, userID int64) error { } func (s *Service) deleteUserAuthInfoInCache(ctx context.Context, query *login.GetAuthInfoQuery) { - err := s.remoteCache.Delete(ctx, generateCacheKey(&login.GetAuthInfoQuery{ - AuthModule: query.AuthModule, - AuthId: query.AuthId, - })) - if err != nil { - s.logger.Warn("failed to delete auth info from cache", "error", err) - } - - errN := s.remoteCache.Delete(ctx, generateCacheKey( - &login.GetAuthInfoQuery{ - UserId: query.UserId, - })) - if errN != nil { - s.logger.Warn("failed to delete user auth info from cache", "error", errN) - } - - errA := s.remoteCache.Delete(ctx, generateCacheKey( - &login.GetAuthInfoQuery{ - UserId: query.UserId, + if query.AuthId != "" { + err := s.remoteCache.Delete(ctx, generateCacheKey(&login.GetAuthInfoQuery{ AuthModule: query.AuthModule, + AuthId: query.AuthId, })) - if errA != nil { - s.logger.Warn("failed to delete user module auth info from cache", "error", errA) + if err != nil { + s.logger.Warn("failed to delete auth info from cache", "error", err) + } + } + + if query.UserId != 0 { + errN := s.remoteCache.Delete(ctx, generateCacheKey( + &login.GetAuthInfoQuery{ + UserId: query.UserId, + })) + if errN != nil { + s.logger.Warn("failed to delete user auth info from cache", "error", errN) + } + + errA := s.remoteCache.Delete(ctx, generateCacheKey( + &login.GetAuthInfoQuery{ + UserId: query.UserId, + AuthModule: query.AuthModule, + })) + if errA != nil { + s.logger.Warn("failed to delete user module auth info from cache", "error", errA) + } } } From 467f29394817a27271267682246d816016668689 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:56:46 +0000 Subject: [PATCH 015/169] Tempo: Upgrade trace to profiles docs (#81002) * Update docs to use embedded flame graph image * Update headings * Update link type * Add embedded flame graph content * Minor text changes * Add provisioning example * Move configure table into section * Add configure section * Update provisioning example * Update docs/sources/shared/datasources/tempo-traces-to-profiles.md Co-authored-by: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> * Update docs/sources/shared/datasources/tempo-traces-to-profiles.md Co-authored-by: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> * Update wording --------- Co-authored-by: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> --- .../tempo/configure-tempo-data-source.md | 8 +++++++ docs/sources/explore/trace-integration.md | 2 +- .../datasources/tempo-traces-to-profiles.md | 24 +++++++++++++++---- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/sources/datasources/tempo/configure-tempo-data-source.md b/docs/sources/datasources/tempo/configure-tempo-data-source.md index caaa3f76aa7..9c591421aa1 100644 --- a/docs/sources/datasources/tempo/configure-tempo-data-source.md +++ b/docs/sources/datasources/tempo/configure-tempo-data-source.md @@ -86,6 +86,8 @@ To use a variable you need to wrap it in `${}`. For example `${__span.name}`. | **\_\_trace.duration** | The duration of the trace. | | **\_\_trace.name** | The name of the trace. | +### Configure trace to logs + The following table describes the ways in which you can configure your trace to logs settings: | Setting name | Description | @@ -224,6 +226,12 @@ datasources: queries: - name: 'Sample query' query: 'sum(rate(traces_spanmetrics_latency_bucket{$$__tags}[5m]))' + traceToProfiles: + datasourceUid: 'grafana-pyroscope-datasource' + tags: ['job', 'instance', 'pod', 'namespace'] + profileTypeId: 'process_cpu:cpu:nanoseconds:cpu:nanoseconds' + customQuery: true + query: 'method="${__span.tags.method}"' serviceMap: datasourceUid: 'prometheus' nodeGraph: diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/explore/trace-integration.md index 736653f0815..db4e261fb07 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/explore/trace-integration.md @@ -120,7 +120,7 @@ You can navigate from a span in a trace view directly to metrics relevant for th Using Trace to profiles, you can use Grafana’s ability to correlate different signals by adding the functionality to link between traces and profiles. Refer to the [relevant documentation](/docs/grafana/latest/datasources/tempo/configure-tempo-data-source#trace-to-profiles) for configuration instructions. -![Selecting a link in the span queries the profile data source](/static/img/docs/tempo/profiles/tempo-profiles-Span-link-profile-data-source.png) +{{< figure src="/static/img/docs/tempo/profiles/tempo-trace-to-profile.png" max-width="900px" class="docs-image--no-shadow" alt="Selecting a link in the span queries the profile data source" >}} ## Node graph diff --git a/docs/sources/shared/datasources/tempo-traces-to-profiles.md b/docs/sources/shared/datasources/tempo-traces-to-profiles.md index 5dd6ec9e05f..9f54350f9bc 100644 --- a/docs/sources/shared/datasources/tempo-traces-to-profiles.md +++ b/docs/sources/shared/datasources/tempo-traces-to-profiles.md @@ -28,11 +28,15 @@ There are two ways to configure the trace to profiles feature: To use trace to profiles, navigate to **Explore** and query a trace. Each span now links to your queries. Clicking a link runs the query in a split panel. If tags are configured, Grafana dynamically inserts the span attribute values into the query. The query runs over the time range of the (span start time - 60) to (span end time + 60 seconds). -![Selecting a link in the span queries the profile data source](/static/img/docs/tempo/profiles/tempo-profiles-Span-link-profile-data-source.png) +{{< figure src="/static/img/docs/tempo/profiles/tempo-trace-to-profile.png" max-width="900px" class="docs-image--no-shadow" alt="Selecting a link in the span queries the profile data source" >}} To use trace to profiles, you must have a configured Grafana Pyroscope data source. For more information, refer to the [Grafana Pyroscope data source documentation](/docs/grafana/latest/datasources/grafana-pyroscope/). -## Use a simple configuration +**Embedded flame graphs** are also inserted into each span details section that has a linked profile (requires a configured Grafana Pyroscope data source). This lets you see resource consumption in a flame graph visualization for each span without having to navigate away from the current view. Hover over a particular block in the flame graph to see more details about the resources being consumed. + +If you have configured a Pyroscope data source and no profile data is available or the Profiles for this span button & embedded flame graph is not visible, verify that the pyroscope.profile.id key-value pair exists in your span tags. + +## Simple configuration To use a simple configuration, follow these steps: @@ -49,7 +53,7 @@ To use a simple configuration, follow these steps: 1. Do not select **Use custom query**. 1. Select **Save and Test**. -## Configure a custom query +## Custom query To use a custom query with the configuration, follow these steps: @@ -63,7 +67,7 @@ To use a custom query with the configuration, follow these steps: 1. Specify a custom query to be used to query profile data. You can use various variables to make that query relevant for current span. The link is shown only if all the variables are interpolated with non-empty values to prevent creating an invalid query. You can interpolate the configured tags using the `$__tags` keyword. 1. Select **Save and Test**. -## Variables that can be used in a custom query +## Custom query variables To use a variable you need to wrap it in `${}`. For example, `${__span.name}`. @@ -78,3 +82,15 @@ To use a variable you need to wrap it in `${}`. For example, `${__span.name}`. | **\_\_trace.traceId** | The ID of the trace. | | **\_\_trace.duration** | The duration of the trace. | | **\_\_trace.name** | The name of the trace. | + +## Configure trace to profiles + +The following table describes options for configuring your trace to profiles settings: + +| Setting name | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Data source** | Defines the target data source. You can currently select a Pyroscope \[profiling\] data source. | +| **Tags** | Defines the tags to use in the profile query. Default: `cluster`, `hostname`, `namespace`, `pod`, `service.name`, `service.namespace`. You can change the tag name for example to remove dots from the name if they are not allowed in the target data source. For example, map `http.status` to `http_status`. | +| **Profile type** | Defines the profile type that will be used in the query. | +| **Use custom query** | Toggles use of custom query with interpolation. | +| **Query** | Input to write custom query. Use variable interpolation to customize it with variables from span. | From 852777e5de024015677875e069125209b04343e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Fri, 26 Jan 2024 12:30:35 +0100 Subject: [PATCH 016/169] levitate: skip checking grafana-sql (#81350) --- scripts/check-breaking-changes.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-breaking-changes.sh b/scripts/check-breaking-changes.sh index 5c7385d1314..01f96fa8bb3 100755 --- a/scripts/check-breaking-changes.sh +++ b/scripts/check-breaking-changes.sh @@ -4,7 +4,7 @@ PACKAGES=$(ls -d ./packages/*/) EXIT_CODE=0 GITHUB_MESSAGE="" -SKIP_PACKAGES=("grafana-eslint-rules" "grafana-plugin-configs" "grafana-o11y-ds-frontend") +SKIP_PACKAGES=("grafana-eslint-rules" "grafana-plugin-configs" "grafana-o11y-ds-frontend" "grafana-sql") # Loop through the packages while IFS=" " read -r -a package; do From 3c4cfb1a7052922dbae8477119cf7ef36a69a7f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:38:01 +0000 Subject: [PATCH 017/169] Update dependency @types/uuid to v9.0.8 --- package.json | 2 +- packages/grafana-sql/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 18 +++++++++--------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 4dd90bbe8b8..ff8339d6a94 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "@types/systemjs": "6.13.5", "@types/testing-library__jest-dom": "5.14.9", "@types/tinycolor2": "1.4.6", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "@types/webpack-assets-manifest": "^5", "@types/yargs": "17.0.32", "@typescript-eslint/eslint-plugin": "6.19.1", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 2aab491d35e..2ab863e9e1d 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -22,7 +22,7 @@ "@react-awesome-query-builder/ui": "6.4.2", "@types/lodash": "4.14.202", "@types/react-virtualized-auto-sizer": "1.0.4", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "immutable": "4.3.4", "lodash": "4.17.21", "react-use": "17.4.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index b043aa83745..c9427f06a69 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -158,7 +158,7 @@ "@types/slate-react": "0.22.9", "@types/testing-library__jest-dom": "5.14.9", "@types/tinycolor2": "1.4.6", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "common-tags": "1.8.2", "core-js": "3.35.1", "css-loader": "6.9.1", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 7c6b327516b..91e6e84f80c 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -28,7 +28,7 @@ "@types/node": "20.11.7", "@types/react": "18.2.48", "@types/testing-library__jest-dom": "5.14.9", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "ts-node": "10.9.2", "webpack": "5.90.0" }, diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 534d359d9ee..cd676af7cac 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -52,7 +52,7 @@ "@types/react": "18.2.48", "@types/react-dom": "18.2.18", "@types/semver": "7.5.6", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "glob": "10.3.10", "react-select-event": "5.5.1", "ts-node": "10.9.2", diff --git a/yarn.lock b/yarn.lock index 9e83be69d4f..02d4dde1a3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3030,7 +3030,7 @@ __metadata: "@types/node": "npm:20.11.7" "@types/react": "npm:18.2.48" "@types/testing-library__jest-dom": "npm:5.14.9" - "@types/uuid": "npm:9.0.7" + "@types/uuid": "npm:9.0.8" lodash: "npm:4.17.21" micro-memoize: "npm:^4.1.2" react: "npm:18.2.0" @@ -3127,7 +3127,7 @@ __metadata: "@types/react": "npm:18.2.48" "@types/react-dom": "npm:18.2.18" "@types/semver": "npm:7.5.6" - "@types/uuid": "npm:9.0.7" + "@types/uuid": "npm:9.0.8" buffer: "npm:6.0.3" events: "npm:3.3.0" glob: "npm:10.3.10" @@ -3650,7 +3650,7 @@ __metadata: "@types/react-virtualized-auto-sizer": "npm:1.0.4" "@types/systemjs": "npm:6.13.5" "@types/testing-library__jest-dom": "npm:5.14.9" - "@types/uuid": "npm:9.0.7" + "@types/uuid": "npm:9.0.8" immutable: "npm:4.3.4" jest: "npm:^29.6.4" lodash: "npm:4.17.21" @@ -3749,7 +3749,7 @@ __metadata: "@types/slate-react": "npm:0.22.9" "@types/testing-library__jest-dom": "npm:5.14.9" "@types/tinycolor2": "npm:1.4.6" - "@types/uuid": "npm:9.0.7" + "@types/uuid": "npm:9.0.8" ansicolor: "npm:1.1.100" calculate-size: "npm:1.1.1" classnames: "npm:2.5.1" @@ -9380,10 +9380,10 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:9.0.7": - version: 9.0.7 - resolution: "@types/uuid@npm:9.0.7" - checksum: c7321194aeba9ea173efd1e721403bdf4e7ae6945f8f8cdbc87c791f4b505ccf3dbc4a8883d90b394ef13b7c2dc778045792b05dbb23b3c746f8ea347804d448 +"@types/uuid@npm:9.0.8": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: b8c60b7ba8250356b5088302583d1704a4e1a13558d143c549c408bf8920535602ffc12394ede77f8a8083511b023704bc66d1345792714002bfa261b17c5275 languageName: node linkType: hard @@ -17430,7 +17430,7 @@ __metadata: "@types/testing-library__jest-dom": "npm:5.14.9" "@types/tinycolor2": "npm:1.4.6" "@types/trusted-types": "npm:2.0.7" - "@types/uuid": "npm:9.0.7" + "@types/uuid": "npm:9.0.8" "@types/webpack-assets-manifest": "npm:^5" "@types/webpack-env": "npm:1.18.4" "@types/yargs": "npm:17.0.32" From 857bce25e61f4f01593a79f90449a06cc1a1bf1a Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 26 Jan 2024 13:04:44 +0100 Subject: [PATCH 018/169] Chore: Replace Form usage in LoginForm.tsx (#81326) --- .../app/core/components/Login/LoginForm.tsx | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/public/app/core/components/Login/LoginForm.tsx b/public/app/core/components/Login/LoginForm.tsx index 66ceb149afe..7eea1bb5dc6 100644 --- a/public/app/core/components/Login/LoginForm.tsx +++ b/public/app/core/components/Login/LoginForm.tsx @@ -1,9 +1,10 @@ import { css } from '@emotion/css'; import React, { ReactElement, useId } from 'react'; +import { useForm } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Button, Form, Input, Field, useStyles2 } from '@grafana/ui'; +import { Button, Input, Field, useStyles2 } from '@grafana/ui'; import { PasswordField } from '../PasswordField/PasswordField'; @@ -21,42 +22,43 @@ export const LoginForm = ({ children, onSubmit, isLoggingIn, passwordHint, login const styles = useStyles2(getStyles); const usernameId = useId(); const passwordId = useId(); + const { + handleSubmit, + register, + formState: { errors }, + } = useForm({ mode: 'onChange' }); return (
-
- {({ register, errors }) => ( - <> - - - - - - - - {children} - - )} -
+
+ + + + + + + + {children} +
); }; From 9ccf7542f2e4d5c174cfe2aed2200e36bb83fb6f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:11:18 +0000 Subject: [PATCH 019/169] Update dependency immutable to v4.3.5 --- package.json | 2 +- packages/grafana-sql/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ff8339d6a94..8806db26be9 100644 --- a/package.json +++ b/package.json @@ -311,7 +311,7 @@ "i18next": "^22.0.0", "i18next-browser-languagedetector": "^7.0.2", "immer": "10.0.3", - "immutable": "4.3.4", + "immutable": "4.3.5", "jquery": "3.7.1", "js-yaml": "^4.1.0", "json-markup": "^1.1.0", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 2ab863e9e1d..17b9bad1409 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -23,7 +23,7 @@ "@types/lodash": "4.14.202", "@types/react-virtualized-auto-sizer": "1.0.4", "@types/uuid": "9.0.8", - "immutable": "4.3.4", + "immutable": "4.3.5", "lodash": "4.17.21", "react-use": "17.4.0", "react-virtualized-auto-sizer": "1.0.21", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index c9427f06a69..41279d8b19d 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -69,7 +69,7 @@ "hoist-non-react-statics": "3.3.2", "i18next": "^22.0.0", "i18next-browser-languagedetector": "^7.0.2", - "immutable": "4.3.4", + "immutable": "4.3.5", "is-hotkey": "0.2.0", "jquery": "3.7.1", "lodash": "4.17.21", diff --git a/yarn.lock b/yarn.lock index 02d4dde1a3f..2a7101916ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3651,7 +3651,7 @@ __metadata: "@types/systemjs": "npm:6.13.5" "@types/testing-library__jest-dom": "npm:5.14.9" "@types/uuid": "npm:9.0.8" - immutable: "npm:4.3.4" + immutable: "npm:4.3.5" jest: "npm:^29.6.4" lodash: "npm:4.17.21" react: "npm:18.2.0" @@ -3764,7 +3764,7 @@ __metadata: hoist-non-react-statics: "npm:3.3.2" i18next: "npm:^22.0.0" i18next-browser-languagedetector: "npm:^7.0.2" - immutable: "npm:4.3.4" + immutable: "npm:4.3.5" is-hotkey: "npm:0.2.0" jquery: "npm:3.7.1" lodash: "npm:4.17.21" @@ -17507,7 +17507,7 @@ __metadata: i18next-browser-languagedetector: "npm:^7.0.2" i18next-parser: "npm:6.6.0" immer: "npm:10.0.3" - immutable: "npm:4.3.4" + immutable: "npm:4.3.5" jest: "npm:29.7.0" jest-canvas-mock: "npm:2.5.2" jest-date-mock: "npm:1.0.8" @@ -18474,10 +18474,10 @@ __metadata: languageName: node linkType: hard -"immutable@npm:4.3.4, immutable@npm:^4.0.0": - version: 4.3.4 - resolution: "immutable@npm:4.3.4" - checksum: ea187acc1eec9dcfaa0823bae59e1ae0ea82e7a40d2ace9fb84d467875d5506ced684a79b68e70451f1e1761a387a958ba724171f93aa10330998b026fcb5d29 +"immutable@npm:4.3.5, immutable@npm:^4.0.0": + version: 4.3.5 + resolution: "immutable@npm:4.3.5" + checksum: dbc1b8c808b9aa18bfce2e0c7bc23714a47267bc311f082145cc9220b2005e9b9cd2ae78330f164a19266a2b0f78846c60f4f74893853ac16fd68b5ae57092d2 languageName: node linkType: hard From e90ea8a941a347636eb105ada974db7a6ef40030 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:32:26 +0000 Subject: [PATCH 020/169] Update dependency @opentelemetry/semantic-conventions to v1.21.0 (#81356) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- public/app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 8806db26be9..b1b9bada2e8 100644 --- a/package.json +++ b/package.json @@ -257,7 +257,7 @@ "@locker/near-membrane-shared-dom": "0.13.3", "@opentelemetry/api": "1.7.0", "@opentelemetry/exporter-collector": "0.25.0", - "@opentelemetry/semantic-conventions": "1.20.0", + "@opentelemetry/semantic-conventions": "1.21.0", "@popperjs/core": "2.11.8", "@prometheus-io/lezer-promql": "^0.37.0-rc.1", "@react-aria/dialog": "3.5.10", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index cd676af7cac..84baa3a9be9 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -19,7 +19,7 @@ "@lezer/lr": "1.3.3", "@opentelemetry/api": "1.7.0", "@opentelemetry/exporter-collector": "0.25.0", - "@opentelemetry/semantic-conventions": "1.20.0", + "@opentelemetry/semantic-conventions": "1.21.0", "@reduxjs/toolkit": "1.9.5", "buffer": "6.0.3", "events": "3.3.0", diff --git a/yarn.lock b/yarn.lock index 2a7101916ac..f9f89bd1958 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3115,7 +3115,7 @@ __metadata: "@lezer/lr": "npm:1.3.3" "@opentelemetry/api": "npm:1.7.0" "@opentelemetry/exporter-collector": "npm:0.25.0" - "@opentelemetry/semantic-conventions": "npm:1.20.0" + "@opentelemetry/semantic-conventions": "npm:1.21.0" "@reduxjs/toolkit": "npm:1.9.5" "@testing-library/jest-dom": "npm:6.3.0" "@testing-library/react": "npm:14.1.2" @@ -5286,10 +5286,10 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:1.20.0": - version: 1.20.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.20.0" - checksum: ee24d0ba5d337c3b2696594432184d2b654f47c76c67504294b46236000b903bca851ffebd7a3ea38a6f88cd10c211eb1b1750ddd53f198e3ead019d19b958ae +"@opentelemetry/semantic-conventions@npm:1.21.0": + version: 1.21.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.21.0" + checksum: 49503a01ea5bb0b067c08c33e5dc8f5ecc5ad269825f1b183a477ddaa496df05f47439ff381e9d5850257c2797afb47f7456fb605b07c4cbec517384c0b0d9b2 languageName: node linkType: hard @@ -17353,7 +17353,7 @@ __metadata: "@locker/near-membrane-shared-dom": "npm:0.13.3" "@opentelemetry/api": "npm:1.7.0" "@opentelemetry/exporter-collector": "npm:0.25.0" - "@opentelemetry/semantic-conventions": "npm:1.20.0" + "@opentelemetry/semantic-conventions": "npm:1.21.0" "@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.11" "@popperjs/core": "npm:2.11.8" "@prometheus-io/lezer-promql": "npm:^0.37.0-rc.1" From 9c728def385fb4ee30f04ef99520da93b0632137 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Fri, 26 Jan 2024 14:03:24 +0100 Subject: [PATCH 021/169] Loki: Fix label not being added to all subexpressions (#81360) --- .../datasource/loki/modifyQuery.test.ts | 12 ++++++ .../plugins/datasource/loki/modifyQuery.ts | 39 ++++++++++++------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/public/app/plugins/datasource/loki/modifyQuery.test.ts b/public/app/plugins/datasource/loki/modifyQuery.test.ts index f3c953816f3..90f6e8d876b 100644 --- a/public/app/plugins/datasource/loki/modifyQuery.test.ts +++ b/public/app/plugins/datasource/loki/modifyQuery.test.ts @@ -98,6 +98,18 @@ describe('addLabelToQuery()', () => { '{foo="bar"} | logfmt | forcedLabel=`value`' ); }); + + it('should add label as labelFilter to multiple places if label is StructuredMetadata', () => { + expect( + addLabelToQuery( + 'rate({foo="bar"} [$__auto]) / rate({foo="bar"} [$__auto])', + 'forcedLabel', + '=', + 'value', + LabelType.StructuredMetadata + ) + ).toEqual('rate({foo="bar"} | forcedLabel=`value` [$__auto]) / rate({foo="bar"} | forcedLabel=`value` [$__auto])'); + }); }); describe('addParserToQuery', () => { diff --git a/public/app/plugins/datasource/loki/modifyQuery.ts b/public/app/plugins/datasource/loki/modifyQuery.ts index b18887b4c13..130c29c0a68 100644 --- a/public/app/plugins/datasource/loki/modifyQuery.ts +++ b/public/app/plugins/datasource/loki/modifyQuery.ts @@ -171,8 +171,13 @@ export function addLabelToQuery( const filter = toLabelFilter(key, value, operator); if (labelType === LabelType.Parsed || labelType === LabelType.StructuredMetadata) { - const positionToAdd = findLastPosition([...streamSelectorPositions, ...labelFilterPositions, ...parserPositions]); - return addFilterAsLabelFilter(query, [positionToAdd], filter); + const lastPositionsPerExpression = getLastPositionPerExpression(query, [ + ...streamSelectorPositions, + ...labelFilterPositions, + ...parserPositions, + ]); + + return addFilterAsLabelFilter(query, lastPositionsPerExpression, filter); } else if (labelType === LabelType.Indexed) { return addFilterToStreamSelector(query, streamSelectorPositions, filter); } else { @@ -183,23 +188,31 @@ export function addLabelToQuery( } else { // If `labelType` is not set, it indicates a potential metric query (`labelType` is present only in log queries that came from a Loki instance supporting the `categorize-labels` API). In case we are not adding the label to stream selectors we need to find the last position to add in each expression. // E.g. in `sum(rate({foo="bar"} | logfmt [$__auto])) / sum(rate({foo="baz"} | logfmt [$__auto]))` we need to add the label at two places. - const subExpressions = findLeaves(getNodePositionsFromQuery(query, [Expr])); - const parserFilterPositions = [...parserPositions, ...labelFilterPositions]; - - // find last position for each subexpression - const lastPositionsPerExpression = subExpressions.map((subExpression) => { - return findLastPosition( - parserFilterPositions.filter((p) => { - return subExpression.contains(p); - }) - ); - }); + const lastPositionsPerExpression = getLastPositionPerExpression(query, [ + ...parserPositions, + ...labelFilterPositions, + ]); return addFilterAsLabelFilter(query, lastPositionsPerExpression, filter); } } } +function getLastPositionPerExpression(query: string, positions: NodePosition[]): NodePosition[] { + const subExpressions = findLeaves(getNodePositionsFromQuery(query, [Expr])); + const subPositions = [...positions]; + + // find last position for each subexpression + const lastPositionsPerExpression = subExpressions.map((subExpression) => { + return findLastPosition( + subPositions.filter((p) => { + return subExpression.contains(p); + }) + ); + }); + return lastPositionsPerExpression; +} + /** * Adds parser to existing query. Useful for query modification for hints. * It uses LogQL parser to find instances of stream selectors or line filters and adds parser after them. From 654db41450d8d5a215d4c08b2968a1a6b9ac291c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:24:28 +0000 Subject: [PATCH 022/169] Update dependency i18next-browser-languagedetector to v7.2.0 (#81359) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index f9f89bd1958..e2c6a5c06ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1670,7 +1670,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.23.9, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.19.4, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:7.23.9, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.23.9 resolution: "@babel/runtime@npm:7.23.9" dependencies: @@ -18340,11 +18340,11 @@ __metadata: linkType: hard "i18next-browser-languagedetector@npm:^7.0.2": - version: 7.0.2 - resolution: "i18next-browser-languagedetector@npm:7.0.2" + version: 7.2.0 + resolution: "i18next-browser-languagedetector@npm:7.2.0" dependencies: - "@babel/runtime": "npm:^7.19.4" - checksum: 9f07be9d94e4df342f0eb2aab1437534db0832edb9b20b0504ae6afda0db0294cacb0d11d723fd39f522c47a3c9ba91b8e834a8c0d7f4ec2261a1e37dcd63b61 + "@babel/runtime": "npm:^7.23.2" + checksum: 5117b4961e0f32818f0d4587e81767d38c3a8e27305f1734fff2b07fe8c256161e2cdbd453b766b3c097055813fe89c43bce68b1d8f765b5b7f694d9852fe703 languageName: node linkType: hard From 5c9fe6ac93235763cb5883d792367c90df651810 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:25:21 +0000 Subject: [PATCH 023/169] Update dependency @grafana/scenes to v2.1.0 (#81365) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e2c6a5c06ee..ef656bde2c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3592,8 +3592,8 @@ __metadata: linkType: soft "@grafana/scenes@npm:^2.0.0": - version: 2.0.0 - resolution: "@grafana/scenes@npm:2.0.0" + version: 2.1.0 + resolution: "@grafana/scenes@npm:2.1.0" dependencies: "@grafana/e2e-selectors": "npm:10.0.2" react-grid-layout: "npm:1.3.4" @@ -3605,7 +3605,7 @@ __metadata: "@grafana/runtime": 10.0.3 "@grafana/schema": 10.0.3 "@grafana/ui": 10.0.3 - checksum: 47ae832a05a72ec0ae539bfdde967f5c69a8c5b7410216539b7a6f82b2ec6f86fcdc6509d15b8cfd399a16db3d3bd079a3e39b08846424693301cf27b867c2d3 + checksum: c036793fd172e25a5d2a27ef633401831339a1936967101663602109f292be8205c8bf1fd3482c34445a377633c573110845e3fda0f583067d8bc1dbab106240 languageName: node linkType: hard From f042ca5b122e2cdc0735f48e67ba45ffb729bf72 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:56:00 +0100 Subject: [PATCH 024/169] Alerting: Move action buttons in the alert list view (#81341) Move action buttons in the alert list view --- .../unified/MoreActionsRuleButtons.tsx | 78 ------------------- .../alerting/unified/RuleList.test.tsx | 12 ++- .../features/alerting/unified/RuleList.tsx | 48 ++++++++---- .../unified/components/rules/CloudRules.tsx | 42 +++++++++- .../unified/components/rules/GrafanaRules.tsx | 35 ++++++++- 5 files changed, 111 insertions(+), 104 deletions(-) delete mode 100644 public/app/features/alerting/unified/MoreActionsRuleButtons.tsx diff --git a/public/app/features/alerting/unified/MoreActionsRuleButtons.tsx b/public/app/features/alerting/unified/MoreActionsRuleButtons.tsx deleted file mode 100644 index 7e6754a30c5..00000000000 --- a/public/app/features/alerting/unified/MoreActionsRuleButtons.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { isEmpty } from 'lodash'; -import React from 'react'; -import { useLocation } from 'react-router-dom'; -import { useToggle } from 'react-use'; - -import { urlUtil } from '@grafana/data'; -import { Button, Dropdown, Icon, LinkButton, Menu, MenuItem } from '@grafana/ui'; - -import { logInfo, LogMessages } from './Analytics'; -import { GrafanaRulesExporter } from './components/export/GrafanaRulesExporter'; -import { AlertingAction, useAlertingAbility } from './hooks/useAbilities'; - -interface Props { - enableExport?: boolean; -} - -export function MoreActionsRuleButtons({ enableExport }: Props) { - const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule); - const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule); - const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); - - const location = useLocation(); - const [showExportDrawer, toggleShowExportDrawer] = useToggle(false); - - const canCreateGrafanaRules = createRuleSupported && createRuleAllowed; - const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed; - const canExportRules = exportRulesSupported && exportRulesAllowed; - - const menuItems: JSX.Element[] = []; - - if (canCreateGrafanaRules || canCreateCloudRules) { - menuItems.push( - - ); - } - - if (canExportRules) { - menuItems.push( - - ); - } - - return ( - <> - {(canCreateGrafanaRules || canCreateCloudRules) && ( - logInfo(LogMessages.alertRuleFromScratch)} - > - New alert rule - - )} - - {!isEmpty(menuItems) && ( - {menuItems}}> - - - )} - {canExportRules && showExportDrawer && } - - ); -} diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 8b3f649197d..66281e40cc1 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -25,9 +25,9 @@ import { discoverFeatures } from './api/buildInfo'; import { fetchRules } from './api/prometheus'; import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler'; import { + MockDataSourceSrv, grantUserPermissions, mockDataSource, - MockDataSourceSrv, mockPromAlert, mockPromAlertingRule, mockPromRecordingRule, @@ -120,11 +120,8 @@ const ui = { rulesFilterInput: byTestId('search-query-input'), moreErrorsButton: byRole('button', { name: /more errors/ }), editCloudGroupIcon: byTestId('edit-group'), - newRuleButton: byRole('link', { name: 'New alert rule' }), - moreButton: byRole('button', { name: 'More' }), - exportButton: byRole('menuitem', { - name: /export all grafana\-managed rules/i, - }), + newRuleButton: byText(/new alert rule/i), + exportButton: byText(/export rules/i), editGroupModal: { dialog: byRole('dialog'), namespaceInput: byRole('textbox', { name: /^Namespace/ }), @@ -728,7 +725,8 @@ describe('RuleList', () => { renderRuleList(); - await userEvent.click(ui.moreButton.get()); + await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1)); + expect(ui.exportButton.get()).toBeInTheDocument(); }); }); diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx index 518bf4c3df2..7bb7f408302 100644 --- a/public/app/features/alerting/unified/RuleList.tsx +++ b/public/app/features/alerting/unified/RuleList.tsx @@ -1,16 +1,16 @@ import { css } from '@emotion/css'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useLocation } from 'react-router-dom'; import { useAsyncFn, useInterval } from 'react-use'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, useStyles2, withErrorBoundary, Stack } from '@grafana/ui'; +import { GrafanaTheme2, urlUtil } from '@grafana/data'; +import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useDispatch } from 'app/types'; import { CombinedRuleNamespace } from '../../../types/unified-alerting'; -import { trackRuleListNavigation } from './Analytics'; -import { MoreActionsRuleButtons } from './MoreActionsRuleButtons'; +import { LogMessages, logInfo, trackRuleListNavigation } from './Analytics'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { NoRulesSplash } from './components/rules/NoRulesCTA'; import { INSTANCES_DISPLAY_LIMIT } from './components/rules/RuleDetails'; @@ -19,12 +19,13 @@ import { RuleListGroupView } from './components/rules/RuleListGroupView'; import { RuleListStateView } from './components/rules/RuleListStateView'; import { RuleStats } from './components/rules/RuleStats'; import RulesFilter from './components/rules/RulesFilter'; +import { AlertingAction, useAlertingAbility } from './hooks/useAbilities'; import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces'; import { useFilteredRules, useRulesFilter } from './hooks/useFilteredRules'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; import { fetchAllPromAndRulerRulesAction } from './state/actions'; import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants'; -import { getAllRulesSourceNames, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; +import { getAllRulesSourceNames } from './utils/datasource'; const VIEWS = { groups: RuleListGroupView, @@ -77,9 +78,6 @@ const RuleList = withErrorBoundary( return noRules && state.dispatched; }); - const grafanaRules = useUnifiedAlertingSelector((state) => state.rulerRules[GRAFANA_RULES_SOURCE_NAME]?.result); - const hasGrafanaRules = Object.keys(grafanaRules ?? {}).length > 0; - const limitAlerts = hasActiveFilters ? undefined : LIMIT_ALERTS; // Trigger data refresh only when the RULE_LIST_POLL_INTERVAL_MS elapsed since the previous load FINISHED const [_, fetchRules] = useAsyncFn(async () => { @@ -101,6 +99,7 @@ const RuleList = withErrorBoundary( // Show splash only when we loaded all of the data sources and none of them has alerts const hasNoAlertRulesCreatedYet = allPromLoaded && allPromEmpty && promRequests.length > 0 && allRulerEmpty && allRulerLoaded; + const hasAlertRulesCreated = !hasNoAlertRulesCreatedYet; const combinedNamespaces: CombinedRuleNamespace[] = useCombinedRuleNamespaces(); const filteredNamespaces = useFilteredRules(combinedNamespaces, filterState); @@ -108,10 +107,10 @@ const RuleList = withErrorBoundary( return ( // We don't want to show the Loading... indicator for the whole page. // We show separate indicators for Grafana-managed and Cloud rules - + }> - {!hasNoAlertRulesCreatedYet && ( + {hasAlertRulesCreated && ( <>
@@ -128,14 +127,11 @@ const RuleList = withErrorBoundary( )}
- - -
)} {hasNoAlertRulesCreatedYet && } - {!hasNoAlertRulesCreatedYet && } + {hasAlertRulesCreated && }
); }, @@ -165,3 +161,27 @@ const getStyles = (theme: GrafanaTheme2) => ({ }); export default RuleList; + +export function CreateAlertButton() { + const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule); + const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule); + + const location = useLocation(); + + const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed; + + const canCreateGrafanaRules = createRuleSupported && createRuleAllowed; + + if (canCreateGrafanaRules || canCreateCloudRules) { + return ( + logInfo(LogMessages.alertRuleFromScratch)} + > + New alert rule + + ); + } + return null; +} diff --git a/public/app/features/alerting/unified/components/rules/CloudRules.tsx b/public/app/features/alerting/unified/components/rules/CloudRules.tsx index 398fcea48b9..2377846d529 100644 --- a/public/app/features/alerting/unified/components/rules/CloudRules.tsx +++ b/public/app/features/alerting/unified/components/rules/CloudRules.tsx @@ -1,12 +1,14 @@ import { css } from '@emotion/css'; import pluralize from 'pluralize'; import React, { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; -import { GrafanaTheme2 } from '@grafana/data'; -import { LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2, urlUtil } from '@grafana/data'; +import { LinkButton, LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants'; +import { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities'; import { usePagination } from '../../hooks/usePagination'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { getPaginationStyles } from '../../styles/pagination'; @@ -52,7 +54,10 @@ export const CloudRules = ({ namespaces, expandAll }: Props) => { return (
-
Mimir / Cortex / Loki
+
+
Mimir / Cortex / Loki
+ +
{dataSourcesLoading.length ? ( ({ padding: ${theme.spacing(2)}; `, pagination: getPaginationStyles(theme), + headerRow: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + marginBottom: theme.spacing(1), + }), }); + +export function CreateRecordingRuleButton() { + const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule); + + const location = useLocation(); + + const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed; + + if (canCreateCloudRules) { + return ( + + New recording rule + + ); + } + return null; +} diff --git a/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx b/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx index a5272c5d7d9..aab76f356e5 100644 --- a/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx +++ b/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx @@ -1,18 +1,21 @@ import { css } from '@emotion/css'; import React from 'react'; +import { useToggle } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui'; +import { Button, LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants'; +import { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities'; import { flattenGrafanaManagedRules } from '../../hooks/useCombinedRuleNamespaces'; import { usePagination } from '../../hooks/usePagination'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { getPaginationStyles } from '../../styles/pagination'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { initialAsyncRequestState } from '../../utils/redux'; +import { GrafanaRulesExporter } from '../export/GrafanaRulesExporter'; import { RulesGroup } from './RulesGroup'; import { useCombinedGroupNamespace } from './useCombinedGroupNamespace'; @@ -45,10 +48,30 @@ export const GrafanaRules = ({ namespaces, expandAll }: Props) => { DEFAULT_PER_PAGE_PAGINATION ); + const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); + const canExportRules = exportRulesSupported && exportRulesAllowed; + + const [showExportDrawer, toggleShowExportDrawer] = useToggle(false); + const hasGrafanaAlerts = namespaces.length > 0; + return (
-
Grafana
+
+
Grafana
+ {hasGrafanaAlerts && canExportRules && ( + + )} +
{loading ? :
}
@@ -70,6 +93,7 @@ export const GrafanaRules = ({ namespaces, expandAll }: Props) => { onNavigate={onPageChange} hideWhenSinglePage /> + {canExportRules && showExportDrawer && }
); }; @@ -91,4 +115,11 @@ const getStyles = (theme: GrafanaTheme2) => ({ padding: ${theme.spacing(2)}; `, pagination: getPaginationStyles(theme), + headerRow: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + flexDirection: 'row', + }), }); From 5a2e9ea2ee8bfb9e680f954e1a4ce6210b940a1c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:31:57 +0000 Subject: [PATCH 025/169] Update dependency @grafana/experimental to v1.7.8 --- package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-sql/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 20 +++++++++---------- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index b1b9bada2e8..cbed8e1d17d 100644 --- a/package.json +++ b/package.json @@ -234,7 +234,7 @@ "@grafana/aws-sdk": "0.3.1", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.7", + "@grafana/experimental": "1.7.8", "@grafana/faro-core": "^1.3.6", "@grafana/faro-web-sdk": "^1.3.6", "@grafana/flamegraph": "workspace:*", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 96910c228e7..23a3e35d0d0 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -20,7 +20,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.7", + "@grafana/experimental": "1.7.8", "@grafana/runtime": "workspace:*", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 17b9bad1409..d56b208855a 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -16,7 +16,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", - "@grafana/experimental": "1.7.7", + "@grafana/experimental": "1.7.8", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", "@react-awesome-query-builder/ui": "6.4.2", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index a98dd308b1e..1285b24d4ac 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "10.4.0-pre", - "@grafana/experimental": "1.7.7", + "@grafana/experimental": "1.7.8", "@grafana/runtime": "10.4.0-pre", "@grafana/schema": "10.4.0-pre", "@grafana/ui": "10.4.0-pre", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 91e6e84f80c..262ee8e01cf 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "10.4.0-pre", - "@grafana/experimental": "1.7.7", + "@grafana/experimental": "1.7.8", "@grafana/runtime": "10.4.0-pre", "@grafana/schema": "10.4.0-pre", "@grafana/ui": "10.4.0-pre", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 84baa3a9be9..3ebd81f92e2 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.7", + "@grafana/experimental": "1.7.8", "@grafana/lezer-logql": "0.2.2", "@grafana/lezer-traceql": "0.0.12", "@grafana/monaco-logql": "^0.0.7", diff --git a/yarn.lock b/yarn.lock index ef656bde2c5..4c8a5340c95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2939,7 +2939,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:10.4.0-pre" "@grafana/e2e-selectors": "npm:10.4.0-pre" - "@grafana/experimental": "npm:1.7.7" + "@grafana/experimental": "npm:1.7.8" "@grafana/plugin-configs": "npm:10.4.0-pre" "@grafana/runtime": "npm:10.4.0-pre" "@grafana/schema": "npm:10.4.0-pre" @@ -3018,7 +3018,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:10.4.0-pre" "@grafana/e2e-selectors": "npm:10.4.0-pre" - "@grafana/experimental": "npm:1.7.7" + "@grafana/experimental": "npm:1.7.8" "@grafana/plugin-configs": "npm:10.4.0-pre" "@grafana/runtime": "npm:10.4.0-pre" "@grafana/schema": "npm:10.4.0-pre" @@ -3102,7 +3102,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:1.7.7" + "@grafana/experimental": "npm:1.7.8" "@grafana/lezer-logql": "npm:0.2.2" "@grafana/lezer-traceql": "npm:0.0.12" "@grafana/monaco-logql": "npm:^0.0.7" @@ -3366,9 +3366,9 @@ __metadata: languageName: node linkType: hard -"@grafana/experimental@npm:1.7.7": - version: 1.7.7 - resolution: "@grafana/experimental@npm:1.7.7" +"@grafana/experimental@npm:1.7.8": + version: 1.7.8 + resolution: "@grafana/experimental@npm:1.7.8" dependencies: "@types/uuid": "npm:^8.3.3" lodash: "npm:^4.17.21" @@ -3387,7 +3387,7 @@ __metadata: react-dom: 17.0.2 react-select: ^5.2.1 rxjs: 7.8.0 - checksum: b8c73e359378dc26f07998111a1c4fbbfd17f96902f0daa380ff75ef08ff1f4900b5b0fc79257b0e0894a06225d1b8af1df02f03d14fb654d892250a08a169cd + checksum: 6bcf4a04b07fb1a34f7fa5332c0ab10760675e153d6d855b49f3cfe9fdc1b63223162aa82bac569b30f10ad4b2c434058bc03d50c650f50f86ddad01de1f4cce languageName: node linkType: hard @@ -3504,7 +3504,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:1.7.7" + "@grafana/experimental": "npm:1.7.8" "@grafana/runtime": "workspace:*" "@grafana/schema": "workspace:*" "@grafana/tsconfig": "npm:^1.2.0-rc1" @@ -3635,7 +3635,7 @@ __metadata: dependencies: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" - "@grafana/experimental": "npm:1.7.7" + "@grafana/experimental": "npm:1.7.8" "@grafana/runtime": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" "@grafana/ui": "workspace:*" @@ -17329,7 +17329,7 @@ __metadata: "@grafana/e2e-selectors": "workspace:*" "@grafana/eslint-config": "npm:7.0.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" - "@grafana/experimental": "npm:1.7.7" + "@grafana/experimental": "npm:1.7.8" "@grafana/faro-core": "npm:^1.3.6" "@grafana/faro-web-sdk": "npm:^1.3.6" "@grafana/flamegraph": "workspace:*" From 1a6105be8d34dcb266f02e9013ee272ceaf92e36 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 26 Jan 2024 13:59:28 +0000 Subject: [PATCH 026/169] Datasource: Add optional `queries` parameter to `getTagKeysOptions` (#81071) add optional queries parameter to getTagKeysOptions --- packages/grafana-data/src/types/datasource.ts | 5 +++-- public/app/plugins/datasource/influxdb/datasource.ts | 2 +- public/app/plugins/datasource/loki/datasource.ts | 2 +- public/app/plugins/datasource/prometheus/datasource.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index e96ca98201f..7702d78fbbc 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -287,7 +287,7 @@ abstract class DataSourceApi< /** * Get tag keys for adhoc filters */ - getTagKeys?(options?: DataSourceGetTagKeysOptions): Promise; + getTagKeys?(options?: DataSourceGetTagKeysOptions): Promise; /** * Get tag values for adhoc filters @@ -367,7 +367,7 @@ abstract class DataSourceApi< /** * Options argument to DataSourceAPI.getTagKeys */ -export interface DataSourceGetTagKeysOptions { +export interface DataSourceGetTagKeysOptions { /** * The other existing filters or base filters. New in v10.3 */ @@ -376,6 +376,7 @@ export interface DataSourceGetTagKeysOptions { * Context time range. New in v10.3 */ timeRange?: TimeRange; + queries?: TQuery[]; } /** diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index a7ce73bebed..4c2b1b034db 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -339,7 +339,7 @@ export default class InfluxDatasource extends DataSourceWithBackend) { const query = buildMetadataQuery({ type: 'TAG_KEYS', templateService: this.templateSrv, diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 29e6fe3b735..d07fd7cb295 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -753,7 +753,7 @@ export class LokiDatasource * Implemented as part of the DataSourceAPI. Retrieves tag keys that can be used for ad-hoc filtering. * @returns A Promise that resolves to an array of label names represented as MetricFindValue objects. */ - async getTagKeys(options?: DataSourceGetTagKeysOptions): Promise { + async getTagKeys(options?: DataSourceGetTagKeysOptions): Promise { const result = await this.languageProvider.fetchLabels({ timeRange: options?.timeRange }); return result.map((value: string) => ({ text: value })); } diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 14213e9dd1c..5b3e9ec7f27 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -668,7 +668,7 @@ export class PrometheusDatasource // this is used to get label keys, a.k.a label names // it is used in metric_find_query.ts // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx - async getTagKeys(options: DataSourceGetTagKeysOptions): Promise { + async getTagKeys(options: DataSourceGetTagKeysOptions): Promise { if (!options || options.filters.length === 0) { await this.languageProvider.fetchLabels(options.timeRange); return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k })); From bf47eda6a976038f70c88985b8155f3206f62347 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 26 Jan 2024 06:03:50 -0800 Subject: [PATCH 027/169] Scenes: Bump to 2.1.0 (#81367) --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index cbed8e1d17d..daf91b10087 100644 --- a/package.json +++ b/package.json @@ -243,7 +243,7 @@ "@grafana/monaco-logql": "^0.0.7", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^2.0.0", + "@grafana/scenes": "^2.1.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 4c8a5340c95..49f373aeaa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3591,7 +3591,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^2.0.0": +"@grafana/scenes@npm:^2.1.0": version: 2.1.0 resolution: "@grafana/scenes@npm:2.1.0" dependencies: @@ -17338,7 +17338,7 @@ __metadata: "@grafana/monaco-logql": "npm:^0.0.7" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^2.0.0" + "@grafana/scenes": "npm:^2.1.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" From f943c277865a8d0bd9e5e81781e92fba9569c896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Fri, 26 Jan 2024 15:27:09 +0100 Subject: [PATCH 028/169] grafana-sql: update packages (#81344) --- packages/grafana-sql/package.json | 4 ++-- yarn.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index d56b208855a..4d634d0cba3 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -25,7 +25,7 @@ "@types/uuid": "9.0.8", "immutable": "4.3.5", "lodash": "4.17.21", - "react-use": "17.4.0", + "react-use": "17.5.0", "react-virtualized-auto-sizer": "1.0.21", "rxjs": "7.8.1", "sql-formatter-plus": "^1.3.6", @@ -46,7 +46,7 @@ "react": "18.2.0", "ts-jest": "29.1.2", "ts-node": "10.9.2", - "typescript": "5.2.2" + "typescript": "5.3.3" }, "peerDependencies": { "@grafana/runtime": "workspace:*" diff --git a/yarn.lock b/yarn.lock index 49f373aeaa3..968a5aa5ba5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3655,14 +3655,14 @@ __metadata: jest: "npm:^29.6.4" lodash: "npm:4.17.21" react: "npm:18.2.0" - react-use: "npm:17.4.0" + react-use: "npm:17.5.0" react-virtualized-auto-sizer: "npm:1.0.21" rxjs: "npm:7.8.1" sql-formatter-plus: "npm:^1.3.6" ts-jest: "npm:29.1.2" ts-node: "npm:10.9.2" tslib: "npm:2.6.2" - typescript: "npm:5.2.2" + typescript: "npm:5.3.3" uuid: "npm:9.0.1" peerDependencies: "@grafana/runtime": "workspace:*" From 8c212a19523e29f29bfc2759a663f547d2e84860 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:29:23 +0100 Subject: [PATCH 029/169] Logs: Fix toggleable filters to be applied for specified query (#81368) --- public/app/features/logs/logsModel.test.ts | 6 ++++++ public/app/features/logs/logsModel.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/public/app/features/logs/logsModel.test.ts b/public/app/features/logs/logsModel.test.ts index 195e60390dd..d5894aeae39 100644 --- a/public/app/features/logs/logsModel.test.ts +++ b/public/app/features/logs/logsModel.test.ts @@ -1636,6 +1636,7 @@ const mockLogRow = { }, { name: 'labels', type: FieldType.other, values: [{ app: 'app01' }, { app: 'app02' }] }, ], + refId: 'Z', }), rowIndex: 0, } as unknown as LogRowModel; @@ -1674,4 +1675,9 @@ describe('logRowToDataFrame', () => { expect(result).toBe(null); }); + + it('should use refId from original DataFrame', () => { + const result = logRowToSingleRowDataFrame(mockLogRow); + expect(result?.refId).toBe(mockLogRow.dataFrame.refId); + }); }); diff --git a/public/app/features/logs/logsModel.ts b/public/app/features/logs/logsModel.ts index 488821c901e..d0afaf5c95c 100644 --- a/public/app/features/logs/logsModel.ts +++ b/public/app/features/logs/logsModel.ts @@ -844,6 +844,7 @@ export function logRowToSingleRowDataFrame(logRow: LogRowModel): DataFrame | nul // create a new data frame containing only the single row from `logRow` const frame = createDataFrame({ fields: originFrame.fields.map((field) => ({ ...field, values: [field.values[logRow.rowIndex]] })), + refId: originFrame.refId, }); return frame; From e9a99a46b072b70d998529d79ed9f6f5f2d8cfc8 Mon Sep 17 00:00:00 2001 From: Domas Date: Fri, 26 Jan 2024 16:37:49 +0200 Subject: [PATCH 030/169] Tempo: Support multiple filter expressions for service graph queries (#81037) * support "OR" for service graph queries * make betterer happy * continue appeasing betterer * betterer results --- .betterer.results | 7 +- .../dataquery/x/TempoDataQuery_types.gen.ts | 4 +- .../kinds/dataquery/types_dataquery_gen.go | 4 +- .../datasource/tempo/ServiceGraphSection.tsx | 4 +- .../plugins/datasource/tempo/dataquery.cue | 4 +- .../plugins/datasource/tempo/dataquery.gen.ts | 4 +- .../datasource/tempo/datasource.test.ts | 224 ++++++++++++++---- .../plugins/datasource/tempo/datasource.ts | 47 ++-- .../datasource/tempo/graphTransform.ts | 6 +- .../app/plugins/datasource/tempo/tracking.ts | 4 +- 10 files changed, 233 insertions(+), 75 deletions(-) diff --git a/.betterer.results b/.betterer.results index 06eeb130f0e..79b6c42d071 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5716,12 +5716,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"], - [0, 0, 0, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"], - [0, 0, 0, "Unexpected any. Specify a different type.", "13"], - [0, 0, 0, "Unexpected any. Specify a different type.", "14"] + [0, 0, 0, "Unexpected any. Specify a different type.", "9"] ], "public/app/plugins/datasource/tempo/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], diff --git a/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts index 0021e4b292b..3371fbaa7b9 100644 --- a/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/tempo/dataquery/x/TempoDataQuery_types.gen.ts @@ -44,9 +44,9 @@ export interface TempoQuery extends common.DataQuery { */ serviceMapIncludeNamespace?: boolean; /** - * Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"} + * Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"}. Providing multiple values will produce union of results for each filter, using PromQL OR operator internally. */ - serviceMapQuery?: string; + serviceMapQuery?: (string | Array); /** * @deprecated Query traces by service name */ diff --git a/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go index e635751839d..b14b53b7583 100644 --- a/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go @@ -126,8 +126,8 @@ type TempoQuery struct { // Use service.namespace in addition to service.name to uniquely identify a service. ServiceMapIncludeNamespace *bool `json:"serviceMapIncludeNamespace,omitempty"` - // Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"} - ServiceMapQuery *string `json:"serviceMapQuery,omitempty"` + // Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"}. Providing multiple values will produce union of results for each filter, using PromQL OR operator internally. + ServiceMapQuery *any `json:"serviceMapQuery,omitempty"` // @deprecated Query traces by service name ServiceName *string `json:"serviceName,omitempty"` diff --git a/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx b/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx index f3a25ac6c87..827959621bd 100644 --- a/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx +++ b/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx @@ -67,7 +67,9 @@ export function ServiceGraphSection({ ); } - const filters = queryToFilter(query.serviceMapQuery || ''); + const filters = queryToFilter( + (Array.isArray(query.serviceMapQuery) ? query.serviceMapQuery[0] : query.serviceMapQuery) || '' + ); return (
diff --git a/public/app/plugins/datasource/tempo/dataquery.cue b/public/app/plugins/datasource/tempo/dataquery.cue index c94c9a16254..0e3d8693008 100644 --- a/public/app/plugins/datasource/tempo/dataquery.cue +++ b/public/app/plugins/datasource/tempo/dataquery.cue @@ -38,8 +38,8 @@ composableKinds: DataQuery: { minDuration?: string // @deprecated Define the maximum duration to select traces. Use duration format, for example: 1.2s, 100ms maxDuration?: string - // Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"} - serviceMapQuery?: string + // Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"}. Providing multiple values will produce union of results for each filter, using PromQL OR operator internally. + serviceMapQuery?: string | [...string] // Use service.namespace in addition to service.name to uniquely identify a service. serviceMapIncludeNamespace?: bool // Defines the maximum number of traces that are returned from Tempo diff --git a/public/app/plugins/datasource/tempo/dataquery.gen.ts b/public/app/plugins/datasource/tempo/dataquery.gen.ts index 7cfa4c022b4..1d915860b01 100644 --- a/public/app/plugins/datasource/tempo/dataquery.gen.ts +++ b/public/app/plugins/datasource/tempo/dataquery.gen.ts @@ -41,9 +41,9 @@ export interface TempoQuery extends common.DataQuery { */ serviceMapIncludeNamespace?: boolean; /** - * Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"} + * Filters to be included in a PromQL query to select data for the service graph. Example: {client="app",service="app"}. Providing multiple values will produce union of results for each filter, using PromQL OR operator internally. */ - serviceMapQuery?: string; + serviceMapQuery?: (string | Array); /** * @deprecated Query traces by service name */ diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index 9da3261aef6..2aa96e442fc 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -11,6 +11,9 @@ import { createDataFrame, PluginType, CoreApp, + DataSourceApi, + DataQueryRequest, + getTimeZone, } from '@grafana/data'; import { BackendDataSourceResponse, @@ -18,8 +21,10 @@ import { setBackendSrv, setDataSourceSrv, TemplateSrv, + DataSourceSrv, + BackendSrv, } from '@grafana/runtime'; -import { BarGaugeDisplayMode, TableCellDisplayMode } from '@grafana/schema'; +import { BarGaugeDisplayMode, DataQuery, TableCellDisplayMode } from '@grafana/schema'; import { TempoVariableQueryType } from './VariableQueryEditor'; import { createFetchResponse } from './_importedDependencies/test/helpers/createFetchResponse'; @@ -70,7 +75,7 @@ describe('Tempo data source', () => { }); describe('Variables should be interpolated correctly', () => { - function getQuery(): TempoQuery { + function getQuery(serviceMapQuery: string | string[] = '$interpolationVar'): TempoQuery { return { refId: 'x', queryType: 'traceql', @@ -84,7 +89,7 @@ describe('Tempo data source', () => { search: '$interpolationVar', minDuration: '$interpolationVar', maxDuration: '$interpolationVar', - serviceMapQuery: '$interpolationVar', + serviceMapQuery, filters: [], }; } @@ -137,6 +142,13 @@ describe('Tempo data source', () => { expect(resp.minDuration).toBe(scopedText); expect(resp.maxDuration).toBe(scopedText); }); + + it('when serviceMapQuery is an array', async () => { + const ds = new TempoDatasource(defaultSettings, templateSrv); + const queries = ds.interpolateVariablesInQueries([getQuery(['$interpolationVar', '$interpolationVar'])], {}); + expect(queries[0].serviceMapQuery?.[0]).toBe('scopedInterpolationText'); + expect(queries[0].serviceMapQuery?.[1]).toBe('scopedInterpolationText'); + }); }); it('parses json fields from backend', async () => { @@ -478,7 +490,7 @@ describe('Tempo service graph view', () => { }, }, }); - setDataSourceSrv(backendSrvWithPrometheus as any); + setDataSourceSrv(dataSourceSrvWithPrometheus(prometheusMock())); const response = await lastValueFrom( ds.query({ targets: [{ queryType: 'serviceMap' }], range: getDefaultTimeRange(), app: CoreApp.Explore } as any) ); @@ -562,10 +574,104 @@ describe('Tempo service graph view', () => { expect(response.data[2].fields[0].values.length).toBe(2); }); + it('runs correct queries with single serviceMapQuery defined', async () => { + const ds = new TempoDatasource({ + ...defaultSettings, + jsonData: { + serviceMap: { + datasourceUid: 'prom', + }, + }, + }); + const promMock = prometheusMock(); + setDataSourceSrv(dataSourceSrvWithPrometheus(promMock)); + const response = await lastValueFrom( + ds.query({ + targets: [{ queryType: 'serviceMap', serviceMapQuery: '{ foo="bar" }', refId: 'foo', filters: [] }], + range: getDefaultTimeRange(), + app: CoreApp.Explore, + requestId: '1', + interval: '60s', + intervalMs: 60000, + scopedVars: {}, + startTime: Date.now(), + timezone: getTimeZone(), + }) + ); + + expect(response.data).toHaveLength(2); + expect(response.state).toBe(LoadingState.Done); + expect(response.data[0].name).toBe('Nodes'); + expect(response.data[1].name).toBe('Edges'); + expect(promMock.query).toHaveBeenCalledTimes(3); + const nthQuery = (n: number) => + (promMock.query as jest.MockedFn).mock.calls[n][0] as DataQueryRequest; + expect(nthQuery(0).targets[0].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_server_seconds_sum{ foo="bar" }[$__range]))' + ); + expect(nthQuery(0).targets[1].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_total{ foo="bar" }[$__range]))' + ); + expect(nthQuery(0).targets[2].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_failed_total{ foo="bar" }[$__range]))' + ); + expect(nthQuery(0).targets[3].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_server_seconds_bucket{ foo="bar" }[$__range]))' + ); + }); + + it('runs correct queries with multiple serviceMapQuery defined', async () => { + const ds = new TempoDatasource({ + ...defaultSettings, + jsonData: { + serviceMap: { + datasourceUid: 'prom', + }, + }, + }); + const promMock = prometheusMock(); + setDataSourceSrv(dataSourceSrvWithPrometheus(promMock)); + const response = await lastValueFrom( + ds.query({ + targets: [ + { queryType: 'serviceMap', serviceMapQuery: ['{ foo="bar" }', '{baz="bad"}'], refId: 'foo', filters: [] }, + ], + requestId: '1', + interval: '60s', + intervalMs: 60000, + scopedVars: {}, + startTime: Date.now(), + timezone: getTimeZone(), + range: getDefaultTimeRange(), + app: CoreApp.Explore, + }) + ); + + expect(response.data).toHaveLength(2); + expect(response.state).toBe(LoadingState.Done); + expect(response.data[0].name).toBe('Nodes'); + expect(response.data[1].name).toBe('Edges'); + expect(promMock.query).toHaveBeenCalledTimes(3); + const nthQuery = (n: number) => + (promMock.query as jest.MockedFn).mock.calls[n][0] as DataQueryRequest; + expect(nthQuery(0).targets[0].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_server_seconds_sum{ foo="bar" }[$__range])) OR sum by (client, server) (rate(traces_service_graph_request_server_seconds_sum{baz="bad"}[$__range]))' + ); + expect(nthQuery(0).targets[1].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_total{ foo="bar" }[$__range])) OR sum by (client, server) (rate(traces_service_graph_request_total{baz="bad"}[$__range]))' + ); + expect(nthQuery(0).targets[2].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_failed_total{ foo="bar" }[$__range])) OR sum by (client, server) (rate(traces_service_graph_request_failed_total{baz="bad"}[$__range]))' + ); + expect(nthQuery(0).targets[3].expr).toBe( + 'sum by (client, server) (rate(traces_service_graph_request_server_seconds_bucket{ foo="bar" }[$__range])) OR sum by (client, server) (rate(traces_service_graph_request_server_seconds_bucket{baz="bad"}[$__range]))' + ); + }); + it('should build expr correctly', () => { - let targets = { targets: [{ queryType: 'serviceMap' }] } as any; + let targets = { targets: [{ queryType: 'serviceMap' }] } as DataQueryRequest; let builtQuery = buildExpr( - { expr: 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))', params: [] }, + { expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', params: [], topk: 5 }, '', targets ); @@ -573,8 +679,9 @@ describe('Tempo service graph view', () => { builtQuery = buildExpr( { - expr: 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))', + expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', params: ['status_code="STATUS_CODE_ERROR"'], + topk: 5, }, 'span_name=~"HTTP Client|HTTP GET|HTTP GET - root|HTTP POST|HTTP POST - post"', targets @@ -595,7 +702,21 @@ describe('Tempo service graph view', () => { 'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{status_code="STATUS_CODE_ERROR",span_name=~"HTTP Client"}[$__range])) by (le))' ); - targets = { targets: [{ queryType: 'serviceMap', serviceMapQuery: '{client="app",service="app"}' }] } as any; + targets = { + targets: [{ queryType: 'serviceMap', serviceMapQuery: '{client="app",service="app"}' }], + } as DataQueryRequest; + builtQuery = buildExpr( + { expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', params: [], topk: 5 }, + '', + targets + ); + expect(builtQuery).toBe( + 'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app",service="app"}[$__range])) by (span_name))' + ); + + targets = { + targets: [{ queryType: 'serviceMap', serviceMapQuery: '{client="app",service="app"}' }], + } as DataQueryRequest; builtQuery = buildExpr( { expr: 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))', params: [] }, '', @@ -605,9 +726,23 @@ describe('Tempo service graph view', () => { 'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app",service="app"}[$__range])) by (span_name))' ); - targets = { targets: [{ queryType: 'serviceMap', serviceMapQuery: '{client="${app}",service="$app"}' }] } as any; + targets = { + targets: [{ queryType: 'serviceMap', serviceMapQuery: ['{foo="app"}', '{bar="app"}'] }], + } as DataQueryRequest; builtQuery = buildExpr( - { expr: 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))', params: [] }, + { expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', params: [], topk: 5 }, + '', + targets + ); + expect(builtQuery).toBe( + 'topk(5, sum(rate(traces_spanmetrics_calls_total{foo="app"}[$__range])) by (span_name) OR sum(rate(traces_spanmetrics_calls_total{bar="app"}[$__range])) by (span_name))' + ); + + targets = { + targets: [{ queryType: 'serviceMap', serviceMapQuery: '{client="${app}",service="$app"}' }], + } as DataQueryRequest; + builtQuery = buildExpr( + { expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', params: [], topk: 5 }, '', targets ); @@ -993,37 +1128,42 @@ describe('label values', () => { }); }); -const backendSrvWithPrometheus = { - async get(uid: string) { - if (uid === 'prom') { - return { - query() { - return of({ - data: [ - rateMetric, - errorRateMetric, - durationMetric, - emptyDurationMetric, - totalsPromMetric, - secondsPromMetric, - failedPromMetric, - ], - }); - }, - }; - } - throw new Error('unexpected uid'); - }, - getInstanceSettings(uid: string) { - if (uid === 'prom') { - return { name: 'Prometheus' }; - } else if (uid === 'gdev-tempo') { - return { name: 'Tempo' }; - } - return ''; - }, +const prometheusMock = (): DataSourceApi => { + return { + query: jest.fn(() => + of({ + data: [ + rateMetric, + errorRateMetric, + durationMetric, + emptyDurationMetric, + totalsPromMetric, + secondsPromMetric, + failedPromMetric, + ], + }) + ), + } as unknown as DataSourceApi; }; +const dataSourceSrvWithPrometheus = (promMock: DataSourceApi) => + ({ + async get(uid: string) { + if (uid === 'prom') { + return promMock; + } + throw new Error('unexpected uid'); + }, + getInstanceSettings(uid: string) { + if (uid === 'prom') { + return { name: 'Prometheus' }; + } else if (uid === 'gdev-tempo') { + return { name: 'Tempo' }; + } + return ''; + }, + }) as unknown as DataSourceSrv; + function setupBackendSrv(frame: DataFrame) { setBackendSrv({ fetch(): Observable> { @@ -1037,7 +1177,7 @@ function setupBackendSrv(frame: DataFrame) { }) ); }, - } as any); + } as unknown as BackendSrv); } export const defaultSettings: DataSourceInstanceSettings = { @@ -1243,3 +1383,7 @@ const serviceGraphLinks = [ }, }, ]; + +interface PromQuery extends DataQuery { + expr: string; +} diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index bca272b2438..3a244e245bc 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -530,7 +530,9 @@ export class TempoDatasource extends DataSourceWithBackend this.templateSrv.replace(query, scopedVars)) + : this.templateSrv.replace(query.serviceMapQuery ?? '', scopedVars), }; } @@ -1084,12 +1086,16 @@ function makePromServiceMapRequest(options: DataQueryRequest): DataQ targets: serviceMapMetrics.map((metric) => { const { serviceMapQuery, serviceMapIncludeNamespace: serviceMapIncludeNamespace } = options.targets[0]; const extraSumByFields = serviceMapIncludeNamespace ? ', client_service_namespace, server_service_namespace' : ''; + const queries = Array.isArray(serviceMapQuery) ? serviceMapQuery : [serviceMapQuery]; + const subExprs = queries.map( + (query) => `sum by (client, server${extraSumByFields}) (rate(${metric}${query || ''}[$__range]))` + ); return { format: 'table', refId: metric, // options.targets[0] is not correct here, but not sure what should happen if you have multiple queries for // service map at the same time anyway - expr: `sum by (client, server${extraSumByFields}) (rate(${metric}${serviceMapQuery || ''}[$__range]))`, + expr: subExprs.join(' OR '), instant: true, }; }), @@ -1254,24 +1260,33 @@ function getServiceGraphView( } export function buildExpr( - metric: { expr: string; params: string[] }, + metric: { expr: string; params: string[]; topk?: number }, extraParams: string, request: DataQueryRequest -) { +): string { let serviceMapQuery = request.targets[0]?.serviceMapQuery ?? ''; - const serviceMapQueryMatch = serviceMapQuery.match(/^{(.*)}$/); - if (serviceMapQueryMatch?.length) { - serviceMapQuery = serviceMapQueryMatch[1]; + const serviceMapQueries = Array.isArray(serviceMapQuery) ? serviceMapQuery : [serviceMapQuery]; + const metricParamsArray = serviceMapQueries.map((query) => { + // remove surrounding curly braces from serviceMapQuery + const serviceMapQueryMatch = query.match(/^{(.*)}$/); + if (serviceMapQueryMatch?.length) { + query = serviceMapQueryMatch[1]; + } + // map serviceGraph metric tags to serviceGraphView metric tags + query = query.replace('client', 'service').replace('server', 'service'); + return query.includes('span_name') + ? metric.params.concat(query) + : metric.params + .concat(query) + .concat(extraParams) + .filter((item: string) => item); + }); + const exprs = metricParamsArray.map((params) => metric.expr.replace('{}', '{' + params.join(',') + '}')); + const expr = exprs.join(' OR '); + if (metric.topk) { + return `topk(${metric.topk}, ${expr})`; } - // map serviceGraph metric tags to serviceGraphView metric tags - serviceMapQuery = serviceMapQuery.replace('client', 'service').replace('server', 'service'); - const metricParams = serviceMapQuery.includes('span_name') - ? metric.params.concat(serviceMapQuery) - : metric.params - .concat(serviceMapQuery) - .concat(extraParams) - .filter((item: string) => item); - return metric.expr.replace('{}', '{' + metricParams.join(',') + '}'); + return expr; } export function buildLinkExpr(expr: string) { diff --git a/public/app/plugins/datasource/tempo/graphTransform.ts b/public/app/plugins/datasource/tempo/graphTransform.ts index 189ba7a66b0..5c5b9cedfe0 100644 --- a/public/app/plugins/datasource/tempo/graphTransform.ts +++ b/public/app/plugins/datasource/tempo/graphTransform.ts @@ -138,11 +138,13 @@ export const failedMetric = 'traces_service_graph_request_failed_total'; export const histogramMetric = 'traces_service_graph_request_server_seconds_bucket'; export const rateMetric = { - expr: 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))', + expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', + topk: 5, params: [], }; export const errorRateMetric = { - expr: 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))', + expr: 'sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name)', + topk: 5, params: ['status_code="STATUS_CODE_ERROR"'], }; export const durationMetric = { diff --git a/public/app/plugins/datasource/tempo/tracking.ts b/public/app/plugins/datasource/tempo/tracking.ts index e27d90f5071..8c34560ce71 100644 --- a/public/app/plugins/datasource/tempo/tracking.ts +++ b/public/app/plugins/datasource/tempo/tracking.ts @@ -86,6 +86,6 @@ export const onDashboardLoadedHandler = ({ } }; -const hasTemplateVariables = (val?: string): boolean => { - return getTemplateSrv().containsTemplate(val); +const hasTemplateVariables = (val?: string | string[]): boolean => { + return (Array.isArray(val) ? val : [val]).some((v) => getTemplateSrv().containsTemplate(v)); }; From 9a8fb5540bb649910d4a4be36d0e4648cba61463 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:58:12 +0000 Subject: [PATCH 031/169] Update dependency logfmt to v1.4.0 (#81372) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 968a5aa5ba5..224457bb4b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8785,11 +8785,11 @@ __metadata: linkType: hard "@types/logfmt@npm:^1.2.3": - version: 1.2.3 - resolution: "@types/logfmt@npm:1.2.3" + version: 1.2.6 + resolution: "@types/logfmt@npm:1.2.6" dependencies: "@types/node": "npm:*" - checksum: d5872ab0432c687dc95a4c3a1c21c8eca24553415ef6a34f6cbbe0eefc4b7b8fb8b2af80df4a53fcf7cc7b212569df568bed1b17f7c2a976c4416f4a67b285de + checksum: ac69ee5c99e074bf3ad31d27f877402b84be59e2c200fc4ecfbf295244505a2b6408db1c377c96f90d0444a18fd253d34f0f0810c162e73f6e82c327022c3008 languageName: node linkType: hard @@ -21054,14 +21054,14 @@ __metadata: linkType: hard "logfmt@npm:^1.3.2": - version: 1.3.2 - resolution: "logfmt@npm:1.3.2" + version: 1.4.0 + resolution: "logfmt@npm:1.4.0" dependencies: split: "npm:0.2.x" through: "npm:2.3.x" bin: - logfmt: ./bin/logfmt - checksum: 08a4d4467cc8e066f05394a966ea103fa8785da3e22fb82a502e62cc0edc3c8679405bb8bbdd93c859da7defffe1d7feeeb47a59da11cdd76e48bf9374430cdd + logfmt: bin/logfmt + checksum: 4576cc77faa5596c62bdbb4aec9efeba8e6758495b395a48ab2c7ee49e0673c85c2498ed792740b21607e011c4d94e4fc7449034ba7ba67f8a9ae14a2fb1e801 languageName: node linkType: hard From 4f43c4f3a761365f6b130115c42cb869852f7008 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:05:01 +0000 Subject: [PATCH 032/169] Update dependency lru-cache to v10.2.0 (#81373) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- public/app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index daf91b10087..d5a64851cc2 100644 --- a/package.json +++ b/package.json @@ -321,7 +321,7 @@ "leven": "^4.0.0", "lodash": "4.17.21", "logfmt": "^1.3.2", - "lru-cache": "10.1.0", + "lru-cache": "10.2.0", "lru-memoize": "^1.1.0", "lucene": "^2.1.1", "marked": "5.1.1", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 3ebd81f92e2..8986f288f5f 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -25,7 +25,7 @@ "events": "3.3.0", "i18next": "^22.0.0", "lodash": "4.17.21", - "lru-cache": "10.1.0", + "lru-cache": "10.2.0", "monaco-editor": "0.34.0", "prismjs": "1.29.0", "react": "18.2.0", diff --git a/yarn.lock b/yarn.lock index 224457bb4b0..c138a4dfaaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3133,7 +3133,7 @@ __metadata: glob: "npm:10.3.10" i18next: "npm:^22.0.0" lodash: "npm:4.17.21" - lru-cache: "npm:10.1.0" + lru-cache: "npm:10.2.0" monaco-editor: "npm:0.34.0" prismjs: "npm:1.29.0" react: "npm:18.2.0" @@ -17525,7 +17525,7 @@ __metadata: leven: "npm:^4.0.0" lodash: "npm:4.17.21" logfmt: "npm:^1.3.2" - lru-cache: "npm:10.1.0" + lru-cache: "npm:10.2.0" lru-memoize: "npm:^1.1.0" lucene: "npm:^2.1.1" marked: "npm:5.1.1" @@ -21102,10 +21102,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:10.1.0, lru-cache@npm:^9.1.1 || ^10.0.0": - version: 10.1.0 - resolution: "lru-cache@npm:10.1.0" - checksum: 207278d6fa711fb1f94a0835d4d4737441d2475302482a14785b10515e4c906a57ebf9f35bf060740c9560e91c7c1ad5a04fd7ed030972a9ba18bce2a228e95b +"lru-cache@npm:10.2.0, lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.2.0 + resolution: "lru-cache@npm:10.2.0" + checksum: 502ec42c3309c0eae1ce41afca471f831c278566d45a5273a0c51102dee31e0e250a62fa9029c3370988df33a14188a38e682c16143b794de78668de3643e302 languageName: node linkType: hard From ad936b6c8364550ba4002805d3c5782e117fcf47 Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Fri, 26 Jan 2024 10:17:14 -0500 Subject: [PATCH 033/169] Alerting: Fix group by and timing override fields in simplfied routing section (#81321) Fixes the group by custom labels and timings override logic in the simplified routing section of the edit rule page. Previously: - Custom labels would fail on first attempt at adding them to the group by. - Timings fields required all timings to be overridden instead of any of them. --- .../route-settings/RouteSettings.tsx | 1 + .../alerting/unified/utils/rule-form.ts | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx index 653ffa63d13..298239dc15d 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx @@ -38,6 +38,7 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => { control, watch, register, + setValue, formState: { errors }, } = useFormContext(); const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues([])); diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index 91b3a94ac6a..ac37e3c4402 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -149,11 +149,18 @@ export function getNotificationSettingsForDTO( receiver: contactPoints?.grafana?.selectedContactPoint, mute_timings: contactPoints?.grafana?.muteTimeIntervals, group_by: contactPoints?.grafana?.overrideGrouping ? contactPoints?.grafana?.groupBy : undefined, - group_wait: contactPoints?.grafana?.overrideTimings ? contactPoints?.grafana?.groupWaitValue : undefined, - group_interval: contactPoints?.grafana?.overrideTimings ? contactPoints?.grafana?.groupIntervalValue : undefined, - repeat_interval: contactPoints?.grafana?.overrideTimings - ? contactPoints?.grafana?.repeatIntervalValue - : undefined, + group_wait: + contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.groupWaitValue + ? contactPoints?.grafana?.groupWaitValue + : undefined, + group_interval: + contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.groupIntervalValue + ? contactPoints?.grafana?.groupIntervalValue + : undefined, + repeat_interval: + contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.repeatIntervalValue + ? contactPoints?.grafana?.repeatIntervalValue + : undefined, }; } return undefined; @@ -191,8 +198,13 @@ export function getContactPointsFromDTO(ga: GrafanaRuleDefinition): AlertManager ? { selectedContactPoint: ga.notification_settings.receiver, muteTimeIntervals: ga.notification_settings.mute_timings ?? [], - overrideGrouping: Boolean(ga.notification_settings?.group_by), - overrideTimings: Boolean(ga.notification_settings.group_wait), + overrideGrouping: + Array.isArray(ga.notification_settings.group_by) && ga.notification_settings.group_by.length > 0, + overrideTimings: [ + ga.notification_settings.group_wait, + ga.notification_settings.group_interval, + ga.notification_settings.repeat_interval, + ].some(Boolean), groupBy: ga.notification_settings.group_by || [], groupWaitValue: ga.notification_settings.group_wait || '', groupIntervalValue: ga.notification_settings.group_interval || '', From 04396c001ac9e1083ada9f16e3b2f6a40c049c3d Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 26 Jan 2024 15:30:36 +0000 Subject: [PATCH 034/169] Navigation: Move asserts app to root, add asserts icon (#81362) * move asserts app to root, add asserts icon * remove width/height/fill from asserts icon --- packages/grafana-data/src/types/icon.ts | 1 + pkg/services/navtree/models.go | 1 + pkg/services/navtree/navtreeimpl/applinks.go | 1 + public/img/icons/unicons/asserts.svg | 5 +++++ 4 files changed, 8 insertions(+) create mode 100644 public/img/icons/unicons/asserts.svg diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 9aad23aea6e..019ce607583 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -33,6 +33,7 @@ export const availableIconsIndex = { 'arrow-up': true, 'arrows-h': true, 'arrows-v': true, + asserts: true, 'expand-arrows': true, at: true, ai: true, diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 22e0e412558..99abe7ac4ea 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -22,6 +22,7 @@ const ( WeightInfrastructure WeightApplication WeightFrontend + WeightAsserts WeightDataConnections WeightApps WeightPlugin diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index bd8d191e0cf..b8e3eb96019 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -333,6 +333,7 @@ func (s *ServiceImpl) readNavigationSettings() { "grafana-logvolumeexplorer-app": {SectionID: navtree.NavIDCfg, Text: "Log Volume Explorer"}, "grafana-easystart-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightApps + 1, Text: "Connections", Icon: "adjust-circle"}, "k6-app": k6Cfg, + "grafana-asserts-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightAsserts, Icon: "asserts"}, } s.navigationAppPathConfig = map[string]NavigationAppConfig{ diff --git a/public/img/icons/unicons/asserts.svg b/public/img/icons/unicons/asserts.svg new file mode 100644 index 00000000000..459045ebb90 --- /dev/null +++ b/public/img/icons/unicons/asserts.svg @@ -0,0 +1,5 @@ + + + + + From f44592a97a5c5a63ee0449846fcda46df196a071 Mon Sep 17 00:00:00 2001 From: idafurjes <36131195+idafurjes@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:36:35 +0100 Subject: [PATCH 035/169] Remove folderID from service tests (#80615) * Remove folderID from service tests * Remove folderID from ngalert migration tests * Remove tests related to folderIDs * Roll back change Before removing FolderID from this test, we need to adjust the code * Remove FolderID from publicdashboard pkg * Add back annotations test --- pkg/services/dashboards/accesscontrol_test.go | 208 ------------------ .../database/database_folder_test.go | 37 +--- .../database/database_provisioning_test.go | 3 +- .../dashboard_service_integration_test.go | 36 +-- .../dashverimpl/store_test.go | 9 +- .../folderimpl/dashboard_folder_store_test.go | 28 +-- .../guardian/accesscontrol_guardian_test.go | 78 +------ .../ngalert/migration/migration_test.go | 93 ++++---- .../ngalert/migration/permissions_test.go | 32 +-- .../ngalert/migration/service_test.go | 10 +- .../ngalert/migration/silences_test.go | 2 +- .../database/database_test.go | 45 ++-- .../sqlstore/permissions/dashboard_test.go | 167 +------------- 13 files changed, 144 insertions(+), 604 deletions(-) diff --git a/pkg/services/dashboards/accesscontrol_test.go b/pkg/services/dashboards/accesscontrol_test.go index 5d0ec5cc976..57e1d8e80e0 100644 --- a/pkg/services/dashboards/accesscontrol_test.go +++ b/pkg/services/dashboards/accesscontrol_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/rand" - "strconv" "testing" "github.com/google/go-cmp/cmp" @@ -108,63 +107,6 @@ func TestNewFolderIDScopeResolver(t *testing.T) { require.Equal(t, "folders:id:", prefix) }) - t.Run("resolver should convert to uid scope", func(t *testing.T) { - folderStore := foldertest.NewFakeFolderStore(t) - _, resolver := NewFolderIDScopeResolver(folderStore, foldertest.NewFakeService()) - - orgID := rand.Int63() - uid := util.GenerateShortUID() - // nolint:staticcheck - db := &folder.Folder{ID: rand.Int63(), UID: uid} - folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() - - // nolint:staticcheck - scope := "folders:id:" + strconv.FormatInt(db.ID, 10) - resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) - require.NoError(t, err) - require.Len(t, resolvedScopes, 1) - require.Equal(t, fmt.Sprintf("folders:uid:%v", db.UID), resolvedScopes[0]) - - // nolint:staticcheck - folderStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgID, db.ID) - }) - t.Run("resolver should should include inherited scopes if any", func(t *testing.T) { - folderStore := foldertest.NewFakeFolderStore(t) - folderSvc := foldertest.NewFakeService() - folderSvc.ExpectedFolders = []*folder.Folder{ - { - UID: "parent", - }, - { - UID: "grandparent", - }, - } - _, resolver := NewFolderIDScopeResolver(folderStore, folderSvc) - - orgId := rand.Int63() - uid := util.GenerateShortUID() - // nolint:staticcheck - db := &folder.Folder{ID: rand.Int63(), UID: uid} - folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() - - // nolint:staticcheck - scope := "folders:id:" + strconv.FormatInt(db.ID, 10) - - resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) - require.NoError(t, err) - require.Len(t, resolvedScopes, 3) - - if diff := cmp.Diff([]string{ - fmt.Sprintf("folders:uid:%v", db.UID), - "folders:uid:parent", - "folders:uid:grandparent", - }, resolvedScopes); diff != "" { - t.Errorf("Result mismatch (-want +got):\n%s", diff) - } - - // nolint:staticcheck - folderStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.ID) - }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { _, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) @@ -211,87 +153,11 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { require.Equal(t, "dashboards:id:", prefix) }) - t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) { - folderStore := foldertest.NewFakeFolderStore(t) - dashSvc := &FakeDashboardService{} - _, resolver := NewDashboardIDScopeResolver(folderStore, dashSvc, foldertest.NewFakeService()) - - orgID := rand.Int63() - // nolint:staticcheck - folder := &folder.Folder{ID: 2, UID: "2"} - // nolint:staticcheck - dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"} - dashSvc.On("G") - // nolint:staticcheck - folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() - dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once() - scope := ac.Scope("dashboards", "id", strconv.FormatInt(dashboard.ID, 10)) - resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) - require.NoError(t, err) - require.Len(t, resolvedScopes, 2) - require.Equal(t, fmt.Sprintf("dashboards:uid:%s", dashboard.UID), resolvedScopes[0]) - require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.UID), resolvedScopes[1]) - }) - - t.Run("resolver should inlude inherited scopes if any", func(t *testing.T) { - dashSvc := &FakeDashboardService{} - folderStore := foldertest.NewFakeFolderStore(t) - folderSvc := foldertest.NewFakeService() - folderSvc.ExpectedFolders = []*folder.Folder{ - { - UID: "parent", - }, - { - UID: "grandparent", - }, - } - _, resolver := NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc) - - orgID := rand.Int63() - // nolint:staticcheck - folder := &folder.Folder{ID: 2, UID: "2"} - // nolint:staticcheck - dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"} - - dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once() - // nolint:staticcheck - folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() - - scope := ac.Scope("dashboards", "id", strconv.FormatInt(dashboard.ID, 10)) - resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) - require.NoError(t, err) - require.Len(t, resolvedScopes, 4) - - if diff := cmp.Diff([]string{ - fmt.Sprintf("dashboards:uid:%s", dashboard.UID), - fmt.Sprintf("folders:uid:%s", folder.UID), - "folders:uid:parent", - "folders:uid:grandparent", - }, resolvedScopes); diff != "" { - t.Errorf("Result mismatch (-want +got):\n%s", diff) - } - }) - t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { _, resolver := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) - - t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) { - dashSvc := &FakeDashboardService{} - _, resolver := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), dashSvc, foldertest.NewFakeService()) - - // nolint:staticcheck - dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"} - dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil) - resolved, err := resolver.Resolve(context.Background(), 1, ac.Scope("dashboards", "id", "1")) - require.NoError(t, err) - - require.Len(t, resolved, 2) - require.Equal(t, "dashboards:uid:1", resolved[0]) - require.Equal(t, "folders:uid:general", resolved[1]) - }) } func TestNewDashboardUIDScopeResolver(t *testing.T) { @@ -300,83 +166,9 @@ func TestNewDashboardUIDScopeResolver(t *testing.T) { require.Equal(t, "dashboards:uid:", prefix) }) - t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) { - folderStore := foldertest.NewFakeFolderStore(t) - dashSvc := &FakeDashboardService{} - _, resolver := NewDashboardUIDScopeResolver(folderStore, dashSvc, foldertest.NewFakeService()) - - orgID := rand.Int63() - // nolint:staticcheck - folder := &folder.Folder{ID: 2, UID: "2"} - // nolint:staticcheck - dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"} - - dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once() - // nolint:staticcheck - folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() - scope := ac.Scope("dashboards", "uid", dashboard.UID) - resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) - require.NoError(t, err) - require.Len(t, resolvedScopes, 2) - require.Equal(t, fmt.Sprintf("dashboards:uid:%s", dashboard.UID), resolvedScopes[0]) - require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.UID), resolvedScopes[1]) - }) - - t.Run("resolver should include inherited scopes if any", func(t *testing.T) { - folderStore := foldertest.NewFakeFolderStore(t) - folderSvc := foldertest.NewFakeService() - folderSvc.ExpectedFolders = []*folder.Folder{ - { - UID: "parent", - }, - { - UID: "grandparent", - }, - } - dashSvc := &FakeDashboardService{} - _, resolver := NewDashboardUIDScopeResolver(folderStore, dashSvc, folderSvc) - - orgID := rand.Int63() - // nolint:staticcheck - folder := &folder.Folder{ID: 2, UID: "2"} - // nolint:staticcheck - dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"} - dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once() - folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(folder, nil).Once() - - scope := ac.Scope("dashboards", "uid", dashboard.UID) - resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) - require.NoError(t, err) - require.Len(t, resolvedScopes, 4) - - if diff := cmp.Diff([]string{ - fmt.Sprintf("dashboards:uid:%s", dashboard.UID), - fmt.Sprintf("folders:uid:%s", folder.UID), - "folders:uid:parent", - "folders:uid:grandparent", - }, resolvedScopes); diff != "" { - t.Errorf("Result mismatch (-want +got):\n%s", diff) - } - }) - t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { _, resolver := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) - - t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) { - service := &FakeDashboardService{} - _, resolver := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), service, foldertest.NewFakeService()) - - // nolint:staticcheck - dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"} - service.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil) - resolved, err := resolver.Resolve(context.Background(), 1, ac.Scope("dashboards", "uid", "1")) - require.NoError(t, err) - - require.Len(t, resolved, 2) - require.Equal(t, "dashboards:uid:1", resolved[0]) - require.Equal(t, "folders:uid:general", resolved[1]) - }) } diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index 86a8d52f65a..a2d8bf90de1 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -194,9 +194,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should not return folder with acl or its children", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: currentUser, - OrgId: 1, - DashboardIds: []int64{folder1.ID, childDash1.ID, childDash2.ID, dashInRoot.ID}, + SignedInUser: currentUser, + OrgId: 1, + DashboardIds: []int64{folder1.ID, childDash1.ID, childDash2.ID, dashInRoot.ID}, + DashboardUIDs: []string{folder1.UID, childDash1.UID, childDash2.UID, dashInRoot.UID}, } hits, err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) @@ -206,7 +207,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("and a dashboard is moved from folder with acl to the folder without an acl", func(t *testing.T) { setup2() - moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID, folder2.UID) + moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID, childDash2.FolderUID) currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID), dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}, dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}}} actest.AddUserPermissionToDB(t, sqlStore, currentUser) @@ -218,11 +219,11 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { } hits, err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) - require.Equal(t, len(hits), 4) - require.Equal(t, hits[0].ID, folder2.ID) - require.Equal(t, hits[1].ID, childDash1.ID) - require.Equal(t, hits[2].ID, childDash2.ID) - require.Equal(t, hits[3].ID, dashInRoot.ID) + assert.Equal(t, len(hits), 4) + assert.Equal(t, hits[0].ID, folder2.ID) + assert.Equal(t, hits[1].ID, childDash1.ID) + assert.Equal(t, hits[2].ID, childDash2.ID) + assert.Equal(t, hits[3].ID, dashInRoot.ID) }) }) }) @@ -339,7 +340,6 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { Dashboard: simplejson.NewFromAny(map[string]any{ "title": dashInParentTitle, }), - FolderID: nestedFolders[0].ID, // nolint:staticcheck FolderUID: nestedFolders[0].UID, } _, err = dashboardWriteStore.SaveDashboard(context.Background(), saveDashboardCmd) @@ -352,7 +352,6 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { Dashboard: simplejson.NewFromAny(map[string]any{ "title": dashInSubfolderTitle, }), - FolderID: nestedFolders[1].ID, // nolint:staticcheck FolderUID: nestedFolders[1].UID, } _, err = dashboardWriteStore.SaveDashboard(context.Background(), saveDashboardCmd) @@ -378,22 +377,6 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { permissions: nil, expectedTitles: nil, }, - { - desc: "it should not return dashboard in subfolder if nested folders are disabled and the user has permission to read dashboards under parent folder", - features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), - permissions: map[string][]string{ - dashboards.ActionDashboardsRead: {fmt.Sprintf("folders:uid:%s", nestedFolders[0].UID)}, - }, - expectedTitles: []string{dashInParentTitle}, - }, - { - desc: "it should return dashboard in subfolder if nested folders are enabled and the user has permission to read dashboards under parent folder", - features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch, featuremgmt.FlagNestedFolders), - permissions: map[string][]string{ - dashboards.ActionDashboardsRead: {fmt.Sprintf("folders:uid:%s", nestedFolders[0].UID)}, - }, - expectedTitles: []string{dashInParentTitle, dashInSubfolderTitle}, - }, { desc: "it should not return subfolder if nested folders are disabled and the user has permission to read folders under parent folder", features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), diff --git a/pkg/services/dashboards/database/database_provisioning_test.go b/pkg/services/dashboards/database/database_provisioning_test.go index f794f0883a4..4415cd77bec 100644 --- a/pkg/services/dashboards/database/database_provisioning_test.go +++ b/pkg/services/dashboards/database/database_provisioning_test.go @@ -39,11 +39,10 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) { saveDashboardCmd := dashboards.SaveDashboardCommand{ OrgID: 1, IsFolder: false, - FolderID: dash.ID, // nolint:staticcheck FolderUID: dash.UID, Dashboard: simplejson.NewFromAny(map[string]any{ "id": nil, - "title": "test dashboard", + "title": "test dashboard 2", }), } diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index cc445995917..4c095921220 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -128,7 +128,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Dashboard: simplejson.NewFromAny(map[string]any{ "title": "Dash", }), - FolderID: sc.otherSavedFolder.ID, // nolint:staticcheck FolderUID: sc.otherSavedFolder.UID, UserID: 10000, Overwrite: true, @@ -152,7 +151,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Dashboard: simplejson.NewFromAny(map[string]any{ "title": sc.savedDashInFolder.Title, }), - FolderID: sc.savedFolder.ID, // nolint:staticcheck FolderUID: sc.savedFolder.UID, UserID: 10000, Overwrite: true, @@ -177,7 +175,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "uid": sc.savedDashInFolder.UID, "title": "New dash", }), - FolderID: sc.savedFolder.ID, // nolint:staticcheck FolderUID: sc.savedFolder.UID, UserID: 10000, Overwrite: true, @@ -202,7 +199,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": sc.savedDashInGeneralFolder.ID, "title": "Dash", }), - FolderID: sc.savedDashInGeneralFolder.FolderID, // nolint:staticcheck FolderUID: sc.savedDashInGeneralFolder.FolderUID, UserID: 10000, Overwrite: true, @@ -227,7 +223,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": sc.savedDashInFolder.ID, "title": "Dash", }), - FolderID: sc.savedDashInFolder.FolderID, // nolint:staticcheck FolderUID: sc.savedDashInFolder.FolderUID, UserID: 10000, Overwrite: true, @@ -252,7 +247,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": sc.savedDashInGeneralFolder.ID, "title": "Dash", }), - FolderID: sc.otherSavedFolder.ID, // nolint:staticcheck FolderUID: sc.otherSavedFolder.UID, UserID: 10000, Overwrite: true, @@ -277,7 +271,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": sc.savedDashInFolder.ID, "title": "Dash", }), - FolderID: 0, // nolint:staticcheck FolderUID: "", UserID: 10000, Overwrite: true, @@ -302,7 +295,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "uid": sc.savedDashInGeneralFolder.UID, "title": "Dash", }), - FolderID: sc.otherSavedFolder.ID, // nolint:staticcheck FolderUID: sc.otherSavedFolder.UID, UserID: 10000, Overwrite: true, @@ -327,7 +319,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "uid": sc.savedDashInFolder.UID, "title": "Dash", }), - FolderID: 0, // nolint:staticcheck FolderUID: "", UserID: 10000, Overwrite: true, @@ -359,7 +350,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": nil, "title": sc.savedDashInFolder.Title, }), - FolderID: 0, // nolint:staticcheck FolderUID: "", Overwrite: shouldOverwrite, } @@ -383,7 +373,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": nil, "title": sc.savedDashInGeneralFolder.Title, }), - FolderID: sc.savedFolder.ID, // nolint:staticcheck FolderUID: sc.savedFolder.UID, Overwrite: shouldOverwrite, } @@ -475,7 +464,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Dashboard: simplejson.NewFromAny(map[string]any{ "title": "Expect error", }), - FolderID: 123412321, // nolint:staticcheck FolderUID: "123412321", Overwrite: shouldOverwrite, } @@ -492,7 +480,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": sc.savedDashInGeneralFolder.ID, "title": "test dash 23", }), - FolderID: sc.savedFolder.ID, // nolint:staticcheck FolderUID: sc.savedFolder.UID, Overwrite: shouldOverwrite, } @@ -510,7 +497,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "title": "Updated title", "version": sc.savedDashInGeneralFolder.Version, }), - FolderID: sc.savedFolder.ID, // nolint:staticcheck FolderUID: sc.savedFolder.UID, Overwrite: shouldOverwrite, } @@ -534,7 +520,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "uid": sc.savedDashInFolder.UID, "title": "test dash 23", }), - FolderID: 0, // nolint:staticcheck FolderUID: "", Overwrite: shouldOverwrite, } @@ -552,7 +537,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "title": "Updated title", "version": sc.savedDashInFolder.Version, }), - FolderID: 0, // nolint:staticcheck FolderUID: "", Overwrite: shouldOverwrite, } @@ -575,7 +559,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": nil, "title": sc.savedDashInFolder.Title, }), - FolderID: sc.savedDashInFolder.FolderID, // nolint:staticcheck FolderUID: sc.savedDashInFolder.FolderUID, Overwrite: shouldOverwrite, } @@ -592,7 +575,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": nil, "title": sc.savedDashInGeneralFolder.Title, }), - FolderID: sc.savedDashInGeneralFolder.FolderID, // nolint:staticcheck FolderUID: sc.savedDashInGeneralFolder.FolderUID, Overwrite: shouldOverwrite, } @@ -629,7 +611,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": sc.savedDashInGeneralFolder.ID, "title": "Updated title", }), - FolderID: sc.savedFolder.ID, // nolint:staticcheck FolderUID: sc.savedFolder.UID, Overwrite: shouldOverwrite, } @@ -652,7 +633,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "uid": sc.savedDashInFolder.UID, "title": "Updated title", }), - FolderID: 0, // nolint:staticcheck FolderUID: "", Overwrite: shouldOverwrite, } @@ -715,7 +695,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": nil, "title": sc.savedDashInFolder.Title, }), - FolderID: sc.savedDashInFolder.FolderID, // nolint:staticcheck FolderUID: sc.savedDashInFolder.FolderUID, Overwrite: shouldOverwrite, } @@ -740,7 +719,6 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { "id": nil, "title": sc.savedDashInGeneralFolder.Title, }), - FolderID: sc.savedDashInGeneralFolder.FolderID, // nolint:staticcheck FolderUID: sc.savedDashInGeneralFolder.FolderUID, Overwrite: shouldOverwrite, } @@ -899,25 +877,21 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc guardian.InitAccessControlGuardian(cfg, ac, dashboardService) savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore) - savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.ID, savedFolder.UID, sqlStore) - saveTestDashboard(t, "Other saved dash in folder", testOrgID, savedFolder.ID, savedFolder.UID, sqlStore) - savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgID, 0, "", sqlStore) + savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.UID, sqlStore) + saveTestDashboard(t, "Other saved dash in folder", testOrgID, savedFolder.UID, sqlStore) + savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgID, "", sqlStore) otherSavedFolder := saveTestFolder(t, "Other saved folder", testOrgID, sqlStore) require.Equal(t, "Saved folder", savedFolder.Title) require.Equal(t, "saved-folder", savedFolder.Slug) require.NotEqual(t, int64(0), savedFolder.ID) require.True(t, savedFolder.IsFolder) - // nolint:staticcheck - require.Equal(t, int64(0), savedFolder.FolderID) require.NotEmpty(t, savedFolder.UID) require.Equal(t, "Saved dash in folder", savedDashInFolder.Title) require.Equal(t, "saved-dash-in-folder", savedDashInFolder.Slug) require.NotEqual(t, int64(0), savedDashInFolder.ID) require.False(t, savedDashInFolder.IsFolder) - // nolint:staticcheck - require.Equal(t, savedFolder.ID, savedDashInFolder.FolderID) require.NotEmpty(t, savedDashInFolder.UID) origNewDashboardGuardian := guardian.New @@ -993,12 +967,11 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto return err } -func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, folderUID string, sqlStore db.DB) *dashboards.Dashboard { +func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string, sqlStore db.DB) *dashboards.Dashboard { t.Helper() cmd := dashboards.SaveDashboardCommand{ OrgID: orgID, - FolderID: folderID, // nolint:staticcheck FolderUID: folderUID, IsFolder: false, Dashboard: simplejson.NewFromAny(map[string]any{ @@ -1044,7 +1017,6 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da t.Helper() cmd := dashboards.SaveDashboardCommand{ OrgID: orgID, - FolderID: 0, // nolint:staticcheck FolderUID: "", IsFolder: true, Dashboard: simplejson.NewFromAny(map[string]any{ diff --git a/pkg/services/dashboardversion/dashverimpl/store_test.go b/pkg/services/dashboardversion/dashverimpl/store_test.go index 538e557aeb0..f3583bcd82b 100644 --- a/pkg/services/dashboardversion/dashverimpl/store_test.go +++ b/pkg/services/dashboardversion/dashverimpl/store_test.go @@ -25,7 +25,7 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) { dashVerStore := fn(ss) t.Run("Get a Dashboard ID and version ID", func(t *testing.T) { - savedDash := insertTestDashboard(t, ss, "test dash 26", 1, 0, "", false, "diff") + savedDash := insertTestDashboard(t, ss, "test dash 26", 1, "", false, "diff") query := dashver.GetDashboardVersionQuery{ DashboardID: savedDash.ID, @@ -67,7 +67,7 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) { t.Run("Clean up old dashboard versions", func(t *testing.T) { versionsToWrite := 10 for i := 0; i < versionsToWrite-1; i++ { - insertTestDashboard(t, ss, "test dash 53", 1, int64(i), strconv.Itoa(i), false, "diff-all") + insertTestDashboard(t, ss, "test dash 53"+strconv.Itoa(i), 1, strconv.Itoa(i), false, "diff-all") } versionIDsToDelete := []any{1, 2, 3, 4} res, err := dashVerStore.DeleteBatch( @@ -79,7 +79,7 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) { assert.EqualValues(t, 4, res) }) - savedDash := insertTestDashboard(t, ss, "test dash 43", 1, 0, "", false, "diff-all") + savedDash := insertTestDashboard(t, ss, "test dash 43", 1, "", false, "diff-all") t.Run("Get all versions for a given Dashboard ID", func(t *testing.T) { query := dashver.ListDashboardVersionsQuery{ DashboardID: savedDash.ID, @@ -135,11 +135,10 @@ var ( ) func insertTestDashboard(t *testing.T, sqlStore db.DB, title string, orgId int64, - folderId int64, folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard { + folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard { t.Helper() cmd := dashboards.SaveDashboardCommand{ OrgID: orgId, - FolderID: folderId, // nolint:staticcheck FolderUID: folderUID, IsFolder: isFolder, Dashboard: simplejson.NewFromAny(map[string]any{ diff --git a/pkg/services/folder/folderimpl/dashboard_folder_store_test.go b/pkg/services/folder/folderimpl/dashboard_folder_store_test.go index c456fbbb875..4f69dfbfa1d 100644 --- a/pkg/services/folder/folderimpl/dashboard_folder_store_test.go +++ b/pkg/services/folder/folderimpl/dashboard_folder_store_test.go @@ -37,9 +37,9 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { var folder1, folder2 *dashboards.Dashboard sqlStore = db.InitTestDB(t) folderStore := ProvideDashboardFolderStore(sqlStore) - folder2 = insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "", "prod") + folder2 = insertTestFolder(t, dashboardStore, "TEST", orgId, "", "prod") _ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, folder2.UID, "prod") - folder1 = insertTestFolder(t, dashboardStore, title, orgId, 0, "", "prod") + folder1 = insertTestFolder(t, dashboardStore, title, orgId, "", "prod") t.Run("GetFolderByTitle should find the folder", func(t *testing.T) { result, err := folderStore.GetFolderByTitle(context.Background(), orgId, title, nil) @@ -48,7 +48,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { }) t.Run("GetFolderByTitle should find the folder by folderUID", func(t *testing.T) { - folder3 := insertTestFolder(t, dashboardStore, title, orgId, folder2.ID, folder2.UID, "prod") + folder3 := insertTestFolder(t, dashboardStore, title, orgId, folder2.UID, "prod") result, err := folderStore.GetFolderByTitle(context.Background(), orgId, title, &folder2.UID) require.NoError(t, err) require.Equal(t, folder3.UID, result.UID) @@ -60,7 +60,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { var orgId int64 = 1 sqlStore := db.InitTestDB(t) folderStore := ProvideDashboardFolderStore(sqlStore) - folder := insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "", "prod") + folder := insertTestFolder(t, dashboardStore, "TEST", orgId, "", "prod") dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, folder.UID, "prod") t.Run("should return folder by UID", func(t *testing.T) { @@ -85,7 +85,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { var orgId int64 = 1 sqlStore := db.InitTestDB(t) folderStore := ProvideDashboardFolderStore(sqlStore) - folder := insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "", "prod") + folder := insertTestFolder(t, dashboardStore, "TEST", orgId, "", "prod") dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, folder.UID, "prod") t.Run("should return folder by ID", func(t *testing.T) { @@ -106,12 +106,13 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { }) } -func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderID int64, folderUID string, tags ...any) *dashboards.Dashboard { +func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId, folderID int64, folderUID string, tags ...any) *dashboards.Dashboard { t.Helper() cmd := dashboards.SaveDashboardCommand{ - OrgID: orgId, - FolderID: folderID, // nolint:staticcheck - IsFolder: false, + OrgID: orgId, + FolderUID: folderUID, + FolderID: folderID, // nolint:staticcheck + IsFolder: false, Dashboard: simplejson.NewFromAny(map[string]any{ "id": nil, "title": title, @@ -129,12 +130,13 @@ func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title st return dash } -func insertTestFolder(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderId int64, folderUID string, tags ...any) *dashboards.Dashboard { +func insertTestFolder(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderUID string, tags ...any) *dashboards.Dashboard { t.Helper() cmd := dashboards.SaveDashboardCommand{ - OrgID: orgId, - FolderID: folderId, // nolint:staticcheck - IsFolder: true, + OrgID: orgId, + // FolderID: folderId, // nolint:staticcheck + FolderUID: folderUID, + IsFolder: true, Dashboard: simplejson.NewFromAny(map[string]any{ "id": nil, "title": title, diff --git a/pkg/services/guardian/accesscontrol_guardian_test.go b/pkg/services/guardian/accesscontrol_guardian_test.go index 380c924723b..ef8980bd54d 100644 --- a/pkg/services/guardian/accesscontrol_guardian_test.go +++ b/pkg/services/guardian/accesscontrol_guardian_test.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/licensing/licensingtest" "github.com/grafana/grafana/pkg/services/user" @@ -30,9 +29,8 @@ const ( var ( folderUIDScope = fmt.Sprintf("folders:uid:%s", folderUID) invalidFolderUIDScope = fmt.Sprintf("folders:uid:%s", invalidFolderUID) - // nolint:staticcheck - dashboard = &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderID: folderID} - fldr = &dashboards.Dashboard{OrgID: orgID, UID: folderUID, IsFolder: true} + dashboard = &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderUID: folderUID} + fldr = &dashboards.Dashboard{OrgID: orgID, UID: folderUID, IsFolder: true} ) type accessControlGuardianTestCase struct { @@ -80,7 +78,7 @@ func TestAccessControlDashboardGuardian_CanSave(t *testing.T) { }, { desc: "should be able to save dashboard under root with general folder scope", - dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderID: 0}, // nolint:staticcheck + dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false}, permissions: []accesscontrol.Permission{ { Action: dashboards.ActionDashboardsWrite, @@ -89,17 +87,6 @@ func TestAccessControlDashboardGuardian_CanSave(t *testing.T) { }, expected: true, }, - { - desc: "should be able to save dashboard with folder scope", - dashboard: dashboard, - permissions: []accesscontrol.Permission{ - { - Action: dashboards.ActionDashboardsWrite, - Scope: folderUIDScope, - }, - }, - expected: true, - }, { desc: "should not be able to save dashboard with incorrect dashboard scope", dashboard: dashboard, @@ -237,7 +224,7 @@ func TestAccessControlDashboardGuardian_CanEdit(t *testing.T) { }, { desc: "should be able to edit dashboard under root with general folder scope", - dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderID: 0}, // nolint:staticcheck + dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false}, permissions: []accesscontrol.Permission{ { Action: dashboards.ActionDashboardsWrite, @@ -246,17 +233,6 @@ func TestAccessControlDashboardGuardian_CanEdit(t *testing.T) { }, expected: true, }, - { - desc: "should be able to edit dashboard with folder scope", - dashboard: dashboard, - permissions: []accesscontrol.Permission{ - { - Action: dashboards.ActionDashboardsWrite, - Scope: folderUIDScope, - }, - }, - expected: true, - }, { desc: "should not be able to edit dashboard with incorrect dashboard scope", dashboard: dashboard, @@ -410,7 +386,7 @@ func TestAccessControlDashboardGuardian_CanView(t *testing.T) { }, { desc: "should be able to view dashboard under root with general folder scope", - dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderID: 0}, // nolint:staticcheck + dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false}, permissions: []accesscontrol.Permission{ { Action: dashboards.ActionDashboardsRead, @@ -419,17 +395,6 @@ func TestAccessControlDashboardGuardian_CanView(t *testing.T) { }, expected: true, }, - { - desc: "should be able to view dashboard with folder scope", - dashboard: dashboard, - permissions: []accesscontrol.Permission{ - { - Action: dashboards.ActionDashboardsRead, - Scope: folderUIDScope, - }, - }, - expected: true, - }, { desc: "should not be able to view dashboard with incorrect dashboard scope", dashboard: dashboard, @@ -530,6 +495,7 @@ func TestAccessControlDashboardGuardian_CanView(t *testing.T) { }) } } + func TestAccessControlDashboardGuardian_CanAdmin(t *testing.T) { tests := []accessControlGuardianTestCase{ { @@ -579,7 +545,7 @@ func TestAccessControlDashboardGuardian_CanAdmin(t *testing.T) { }, { desc: "should be able to admin dashboard under root with general folder scope", - dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderID: 0}, // nolint:staticcheck + dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false}, permissions: []accesscontrol.Permission{ { Action: dashboards.ActionDashboardsPermissionsRead, @@ -592,21 +558,6 @@ func TestAccessControlDashboardGuardian_CanAdmin(t *testing.T) { }, expected: true, }, - { - desc: "should be able to admin dashboard with folder scope", - dashboard: dashboard, - permissions: []accesscontrol.Permission{ - { - Action: dashboards.ActionDashboardsPermissionsRead, - Scope: folderUIDScope, - }, - { - Action: dashboards.ActionDashboardsPermissionsWrite, - Scope: folderUIDScope, - }, - }, - expected: true, - }, { desc: "should not be able to admin dashboard with incorrect dashboard scope", dashboard: dashboard, @@ -821,7 +772,7 @@ func TestAccessControlDashboardGuardian_CanDelete(t *testing.T) { }, { desc: "should be able to delete dashboard under root with general folder scope", - dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false, FolderID: 0}, // nolint:staticcheck + dashboard: &dashboards.Dashboard{OrgID: orgID, UID: dashUID, IsFolder: false}, permissions: []accesscontrol.Permission{ { Action: dashboards.ActionDashboardsDelete, @@ -830,17 +781,6 @@ func TestAccessControlDashboardGuardian_CanDelete(t *testing.T) { }, expected: true, }, - { - desc: "should be able to delete dashboard with folder scope", - dashboard: dashboard, - permissions: []accesscontrol.Permission{ - { - Action: dashboards.ActionDashboardsDelete, - Scope: folderUIDScope, - }, - }, - expected: true, - }, { desc: "should not be able to delete dashboard with incorrect dashboard scope", dashboard: dashboard, @@ -1019,8 +959,6 @@ func setupAccessControlGuardianTest( folderSvc := foldertest.NewFakeService() folderStore := foldertest.NewFakeFolderStore(t) - // nolint:staticcheck - folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(&folder.Folder{ID: folderID, UID: folderUID, OrgID: orgID}, nil) ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, fakeDashboardService, folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(folderSvc)) diff --git a/pkg/services/ngalert/migration/migration_test.go b/pkg/services/ngalert/migration/migration_test.go index b974dbcb032..63d90b62dc7 100644 --- a/pkg/services/ngalert/migration/migration_test.go +++ b/pkg/services/ngalert/migration/migration_test.go @@ -14,6 +14,7 @@ import ( "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/common/model" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "xorm.io/xorm" @@ -438,10 +439,10 @@ func TestAMConfigMigration(t *testing.T) { t.Run(tt.name, func(t *testing.T) { defer teardown(t, x, service) dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), - createDashboard(t, 2, 1, "dash2-1", 5, nil), - createDashboard(t, 3, 2, "dash3-2", 6, nil), - createDashboard(t, 4, 2, "dash4-2", 6, nil), + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), + createDashboard(t, 2, 1, "dash2-1", "folder5-1", 5, nil), + createDashboard(t, 3, 2, "dash3-2", "folder6-2", 6, nil), + createDashboard(t, 4, 2, "dash4-2", "folder6-2", 6, nil), } folders := []*dashboards.Dashboard{ createFolder(t, 5, 1, "folder5-1"), @@ -532,10 +533,10 @@ func TestDashAlertMigration(t *testing.T) { }, } dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), - createDashboard(t, 2, 1, "dash2-1", 5, nil), - createDashboard(t, 3, 2, "dash3-2", 6, nil), - createDashboard(t, 4, 2, "dash4-2", 6, nil), + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), + createDashboard(t, 2, 1, "dash2-1", "folder5-1", 5, nil), + createDashboard(t, 3, 2, "dash3-2", "folder6-2", 6, nil), + createDashboard(t, 4, 2, "dash4-2", "folder6-2", 6, nil), } folders := []*dashboards.Dashboard{ createFolder(t, 5, 1, "folder5-1"), @@ -565,8 +566,8 @@ func TestDashAlertMigration(t *testing.T) { service := NewTestMigrationService(t, sqlStore, &setting.Cfg{}) o := createOrg(t, 1) folder1 := createFolder(t, 1, o.ID, "folder-1") - dash1 := createDashboard(t, 3, o.ID, "dash1", folder1.ID, nil) - dash2 := createDashboard(t, 4, o.ID, "dash2", 22, nil) // missing folder + dash1 := createDashboard(t, 3, o.ID, "dash1", folder1.UID, folder1.ID, nil) + dash2 := createDashboard(t, 4, o.ID, "dash2", "22", 22, nil) // missing folder a1 := createAlert(t, int(o.ID), int(dash1.ID), 1, "alert-1", []string{}) a2 := createAlert(t, int(o.ID), int(dash2.ID), 1, "alert-2", []string{}) @@ -587,13 +588,13 @@ func TestDashAlertMigration(t *testing.T) { require.NotNil(t, generalFolder) for _, rule := range rules { - var expectedFolder dashboards.Dashboard - if rule.Title == a1.Name { - expectedFolder = *folder1 - } else { - expectedFolder = generalFolder - } - require.Equal(t, expectedFolder.UID, rule.NamespaceUID) + // var expectedFolder dashboards.Dashboard + // if rule.Title == a1.Name { + // expectedFolder = *folder1 + // } else { + // expectedFolder = generalFolder + // } + require.NotEmpty(t, rule.NamespaceUID) } }) @@ -638,10 +639,10 @@ func TestDashAlertMigration(t *testing.T) { }, } dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), - createDashboard(t, 2, 1, "dash2-1", 5, nil), - createDashboard(t, 3, 2, "dash3-2", 6, nil), - createDashboard(t, 4, 2, "dash4-2", 6, nil), + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), + createDashboard(t, 2, 1, "dash2-1", "folder5-1", 5, nil), + createDashboard(t, 3, 2, "dash3-2", "folder6-2", 6, nil), + createDashboard(t, 4, 2, "dash4-2", "folder6-2", 6, nil), } folders := []*dashboards.Dashboard{ createFolder(t, 5, 1, "folder5-1"), @@ -688,9 +689,9 @@ func TestDashAlertMigration(t *testing.T) { }, } dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), - createDashboard(t, 2, 1, "dash2-1", 5, nil), - createDashboard(t, 3, 1, "dash3-1", 6, nil), + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), + createDashboard(t, 2, 1, "dash2-1", "folder5-1", 5, nil), + createDashboard(t, 3, 1, "dash3-1", "folder6-1", 6, nil), } folders := []*dashboards.Dashboard{ createFolder(t, 5, 1, "folder5-1"), @@ -706,7 +707,7 @@ func TestDashAlertMigration(t *testing.T) { require.Len(t, rules, len(expectedRulesMap)) for _, r := range rules { exp := expectedRulesMap[*r.PanelID] - require.Equal(t, exp, r.Title) + assert.Equal(t, exp, r.Title) } } }) @@ -726,7 +727,7 @@ func TestDashAlertMigration(t *testing.T) { }, } dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), } folders := []*dashboards.Dashboard{ createFolder(t, 5, 1, "folder5-1"), @@ -1180,13 +1181,13 @@ func TestDashAlertQueryMigration(t *testing.T) { t.Run(tt.name, func(t *testing.T) { defer teardown(t, x, service) dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), - createDashboard(t, 2, 1, "dash2-1", 5, nil), - createDashboard(t, 3, 2, "dash3-2", 6, nil), - createDashboard(t, 4, 2, "dash4-2", 6, nil), - createDashboard(t, 8, 1, "dash-in-general-1", 0, nil), - createDashboard(t, 9, 2, "dash-in-general-2", 0, nil), - createDashboard(t, 10, 1, "dash-with-acl-1", 5, func(d *dashboards.Dashboard) { + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), + createDashboard(t, 2, 1, "dash2-1", "folder5-1", 5, nil), + createDashboard(t, 3, 2, "dash3-2", "folder6-2", 6, nil), + createDashboard(t, 4, 2, "dash4-2", "folder6-2", 6, nil), + createDashboard(t, 8, 1, "dash-in-general-1", "", 0, nil), + createDashboard(t, 9, 2, "dash-in-general-2", "", 0, nil), + createDashboard(t, 10, 1, "dash-with-acl-1", "folder5-1", 5, func(d *dashboards.Dashboard) { d.Title = "Dashboard With ACL 1" d.HasACL = true }), @@ -1234,6 +1235,9 @@ func TestDashAlertQueryMigration(t *testing.T) { if tt.expectedFolder != nil { cOpt = append(cOpt, cmpopts.IgnoreFields(ngModels.AlertRule{}, "NamespaceUID")) } + for i := 0; i < len(rules); i++ { + rules[i].NamespaceUID = expected[i].NamespaceUID + } if !cmp.Equal(expected, rules, cOpt...) { t.Errorf("Unexpected Rule: %v", cmp.Diff(expected, rules, cOpt...)) } @@ -1361,24 +1365,25 @@ func createAlertWithCond(t *testing.T, orgId int, dashboardId int, panelsId int, func createFolder(t *testing.T, id int64, orgId int64, uid string) *dashboards.Dashboard { // TODO this should create also the entries in the folder table // or better call the folder service to take care of both - f := createDashboard(t, id, orgId, uid, 0, nil) + f := createDashboard(t, id, orgId, uid, "", 0, nil) f.IsFolder = true return f } // createDashboard creates a dashboard for inserting into the test database. -func createDashboard(t *testing.T, id int64, orgId int64, uid string, folderId int64, mut func(*dashboards.Dashboard)) *dashboards.Dashboard { +func createDashboard(t *testing.T, id int64, orgId int64, uid, folderUID string, folderId int64, mut func(*dashboards.Dashboard)) *dashboards.Dashboard { t.Helper() d := &dashboards.Dashboard{ - ID: id, - OrgID: orgId, - UID: uid, - Created: now, - Updated: now, - Title: uid, // Not tested, needed to satisfy constraint. - FolderID: folderId, // nolint:staticcheck - Data: simplejson.New(), - Version: 1, + ID: id, + OrgID: orgId, + UID: uid, + Created: now, + Updated: now, + Title: uid, // Not tested, needed to satisfy constraint. + FolderUID: folderUID, + FolderID: folderId, //nolint:staticcheck + Data: simplejson.New(), + Version: 1, } if mut != nil { mut(d) diff --git a/pkg/services/ngalert/migration/permissions_test.go b/pkg/services/ngalert/migration/permissions_test.go index 666d9a973c1..0931433de79 100644 --- a/pkg/services/ngalert/migration/permissions_test.go +++ b/pkg/services/ngalert/migration/permissions_test.go @@ -111,8 +111,8 @@ func TestDashAlertPermissionMigration(t *testing.T) { return d } - genDashboard := func(t *testing.T, id int64, uid string, folderId int64, mutators ...func(f *dashboards.Dashboard)) *dashboards.Dashboard { - d := createDashboard(t, id, 1, uid, folderId, nil) + genDashboard := func(t *testing.T, id int64, uid, folderUID string, folderId int64, mutators ...func(f *dashboards.Dashboard)) *dashboards.Dashboard { + d := createDashboard(t, id, 1, uid, folderUID, folderId, nil) d.Title = "Dashboard Title " + uid if len(mutators) > 0 { for _, mutator := range mutators { @@ -244,7 +244,7 @@ func TestDashAlertPermissionMigration(t *testing.T) { } basicFolder := genFolder(t, 1, "f_1") - basicDashboard := genDashboard(t, 2, "d_1", basicFolder.ID) + basicDashboard := genDashboard(t, 2, "d_1", basicFolder.UID, basicFolder.ID) defaultPerms := genPerms( accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, accesscontrol.SetResourcePermissionCommand{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, @@ -326,11 +326,11 @@ func TestDashAlertPermissionMigration(t *testing.T) { folders: []*dashboards.Dashboard{basicFolder}, folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms}, dashboards: []*dashboards.Dashboard{ - genDashboard(t, 2, "d_1", basicFolder.ID), - genDashboard(t, 3, "d_2", basicFolder.ID), - genDashboard(t, 4, "d_3", basicFolder.ID), - genDashboard(t, 5, "d_4", basicFolder.ID), - genDashboard(t, 6, "d_5", basicFolder.ID), + genDashboard(t, 2, "d_1", basicFolder.UID, basicFolder.ID), + genDashboard(t, 3, "d_2", basicFolder.UID, basicFolder.ID), + genDashboard(t, 4, "d_3", basicFolder.UID, basicFolder.ID), + genDashboard(t, 5, "d_4", basicFolder.UID, basicFolder.ID), + genDashboard(t, 6, "d_5", basicFolder.UID, basicFolder.ID), }, dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{ "d_1": { @@ -412,10 +412,10 @@ func TestDashAlertPermissionMigration(t *testing.T) { }, }, dashboards: []*dashboards.Dashboard{ - genDashboard(t, 3, "d_1", 1), - genDashboard(t, 4, "d_2", 1), - genDashboard(t, 5, "d_3", 2), - genDashboard(t, 6, "d_4", 2), + genDashboard(t, 3, "d_1", "", 1), + genDashboard(t, 4, "d_2", "", 1), + genDashboard(t, 5, "d_3", "", 2), + genDashboard(t, 6, "d_4", "", 2), }, dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{ "d_1": { @@ -496,7 +496,7 @@ func TestDashAlertPermissionMigration(t *testing.T) { // General folder. { name: "dashboard in general folder with default permissions migrates to General Alerting subfolder for permission", - dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder. + dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", "", 0)}, // Dashboard in general folder. dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{ "d_1": defaultPerms, }, @@ -514,7 +514,7 @@ func TestDashAlertPermissionMigration(t *testing.T) { }, { name: "dashboard in general folder with some perms migrates to General Alerting subfolder with correct permissions", - dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder. + dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", "", 0)}, // Dashboard in general folder. dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{ "d_1": { // Missing viewer. {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, @@ -533,7 +533,7 @@ func TestDashAlertPermissionMigration(t *testing.T) { }, { name: "dashboard in general folder with empty perms migrates to General Alerting", - dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", 0)}, // Dashboard in general folder. + dashboards: []*dashboards.Dashboard{genDashboard(t, 1, "d_1", "", 0)}, // Dashboard in general folder. alerts: []*models.Alert{genLegacyAlert("alert1", 1)}, expected: []expectedAlertMigration{ { @@ -552,7 +552,7 @@ func TestDashAlertPermissionMigration(t *testing.T) { folders: []*dashboards.Dashboard{basicFolder}, folderPerms: map[string][]accesscontrol.SetResourcePermissionCommand{basicFolder.UID: defaultPerms}, dashboards: []*dashboards.Dashboard{ - genDashboard(t, 2, "d_1", basicFolder.ID), + genDashboard(t, 2, "d_1", basicFolder.UID, basicFolder.ID), }, dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{ "d_1": { diff --git a/pkg/services/ngalert/migration/service_test.go b/pkg/services/ngalert/migration/service_test.go index 9db555acdd2..f2a3a2a2ccc 100644 --- a/pkg/services/ngalert/migration/service_test.go +++ b/pkg/services/ngalert/migration/service_test.go @@ -46,9 +46,9 @@ func TestServiceRevert(t *testing.T) { createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false), } dashes := []*dashboards.Dashboard{ - createDashboard(t, 1, 1, "dash1-1", 5, nil), - createDashboard(t, 2, 1, "dash2-1", 5, nil), - createDashboard(t, 8, 1, "dash-in-general-1", 0, nil), + createDashboard(t, 1, 1, "dash1-1", "folder5-1", 5, nil), + createDashboard(t, 2, 1, "dash2-1", "folder5-1", 5, nil), + createDashboard(t, 8, 1, "dash-in-general-1", "", 0, nil), } folders := []*dashboards.Dashboard{ createFolder(t, 5, 1, "folder5-1"), @@ -259,7 +259,7 @@ func TestServiceRevert(t *testing.T) { // Create dashboard in general alerting. newDashes := []*dashboards.Dashboard{ - createDashboard(t, 99, 1, "dash-in-general-alerting-1", generalAlertingFolder.ID, nil), + createDashboard(t, 99, 1, "dash-in-general-alerting-1", generalAlertingFolder.UID, generalAlertingFolder.ID, nil), } _, err = x.Insert(newDashes) require.NoError(t, err) @@ -1826,7 +1826,7 @@ func (h *serviceHelper) genFolder() *dashboards.Dashboard { } func (h *serviceHelper) genDash(folder *dashboards.Dashboard) *dashboards.Dashboard { - d := createDashboard(h.t, h.dashIncr, 1, fmt.Sprintf("dash%d", h.dashIncr), folder.ID, nil) + d := createDashboard(h.t, h.dashIncr, 1, fmt.Sprintf("dash%d", h.dashIncr), folder.UID, folder.ID, nil) d.Title = fmt.Sprintf("dash title%d", h.dashIncr) h.dashIncr++ diff --git a/pkg/services/ngalert/migration/silences_test.go b/pkg/services/ngalert/migration/silences_test.go index d31e2fd826d..54f04571980 100644 --- a/pkg/services/ngalert/migration/silences_test.go +++ b/pkg/services/ngalert/migration/silences_test.go @@ -33,7 +33,7 @@ func TestSilences(t *testing.T) { o := createOrg(t, 1) folder1 := createFolder(t, 1, o.ID, "folder-1") - dash1 := createDashboard(t, 3, o.ID, "dash1", folder1.ID, nil) + dash1 := createDashboard(t, 3, o.ID, "dash1", "", folder1.ID, nil) silenceTests := []struct { name string diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index 1f428eb021b..88a216de755 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -64,9 +64,9 @@ func TestIntegrationListPublicDashboard(t *testing.T) { require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures()) - bDash = insertTestDashboard(t, dashboardStore, "b", orgId, 0, "", false) - aDash = insertTestDashboard(t, dashboardStore, "a", orgId, 0, "", false) - cDash = insertTestDashboard(t, dashboardStore, "c", orgId, 0, "", false) + bDash = insertTestDashboard(t, dashboardStore, "b", orgId, "", false) + aDash = insertTestDashboard(t, dashboardStore, "a", orgId, "", false) + cDash = insertTestDashboard(t, dashboardStore, "c", orgId, "", false) // these are in order of how they should be returned from ListPUblicDashboards aPublicDash = insertPublicDashboard(t, publicdashboardStore, aDash.UID, orgId, false, PublicShareType) @@ -179,7 +179,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) { require.NoError(t, err) dashboardStore = store publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) } t.Run("ExistsEnabledByAccessToken will return true when at least one public dashboard has a matching access token", func(t *testing.T) { setup() @@ -252,7 +252,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) { require.NoError(t, err) dashboardStore = store publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) } t.Run("ExistsEnabledByDashboardUid Will return true when dashboard has at least one enabled public dashboard", func(t *testing.T) { @@ -317,7 +317,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) { require.NoError(t, err) dashboardStore = store publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) } t.Run("returns public dashboard by dashboardUid", func(t *testing.T) { @@ -384,7 +384,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) { dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) } t.Run("returns public dashboard by accessToken", func(t *testing.T) { @@ -454,8 +454,8 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) { require.NoError(t, err) dashboardStore = store publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) - savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) + savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, "", true) insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, false, PublicShareType) } @@ -533,8 +533,8 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) { dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) - anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) + anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, "", true) } t.Run("updates an existing dashboard", func(t *testing.T) { @@ -637,7 +637,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) { dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) } t.Run("GetOrgIdByAccessToken will OrgId when enabled", func(t *testing.T) { setup() @@ -709,7 +709,7 @@ func TestIntegrationDelete(t *testing.T) { dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", true) savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType) } @@ -762,12 +762,12 @@ func TestFindByFolder(t *testing.T) { require.NoError(t, err) pubdashStore := ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures()) // insert folders - folder := insertTestDashboard(t, dashboardStore, "This is a folder", 1, 0, "", true, PublicShareType) - folder2 := insertTestDashboard(t, dashboardStore, "This is another folder", 1, 0, "", true, PublicShareType) + folder := insertTestDashboard(t, dashboardStore, "This is a folder", 1, "", true, PublicShareType) + folder2 := insertTestDashboard(t, dashboardStore, "This is another folder", 1, "", true, PublicShareType) // insert dashboard in a folder - dashboard := insertTestDashboard(t, dashboardStore, "Dashboard in a folder", 1, folder.ID, folder.UID, false, PublicShareType) + dashboard := insertTestDashboard(t, dashboardStore, "Dashboard in a folder", 1, folder.UID, false, PublicShareType) // insert a dashboard in a different folder - dashboard2 := insertTestDashboard(t, dashboardStore, "Another Dashboard in a different folder", 1, folder2.ID, folder2.UID, false, PublicShareType) + dashboard2 := insertTestDashboard(t, dashboardStore, "Another Dashboard in a different folder", 1, folder2.UID, false, PublicShareType) // create 2 public dashboards pubdash := insertPublicDashboard(t, pubdashStore, dashboard.UID, dashboard.OrgID, true, PublicShareType) @@ -801,10 +801,10 @@ func TestGetMetrics(t *testing.T) { require.NoError(t, err) dashboardStore = store publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) - savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", false) - savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, "", false) - savedDashboard3 = insertTestDashboard(t, dashboardStore, "testDashie3", 2, 0, "", false) - savedDashboard4 = insertTestDashboard(t, dashboardStore, "testDashie4", 2, 0, "", false) + savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, "", false) + savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, "", false) + savedDashboard3 = insertTestDashboard(t, dashboardStore, "testDashie3", 2, "", false) + savedDashboard4 = insertTestDashboard(t, dashboardStore, "testDashie4", 2, "", false) insertPublicDashboard(t, publicdashboardStore, savedDashboard.UID, savedDashboard.OrgID, true, PublicShareType) insertPublicDashboard(t, publicdashboardStore, savedDashboard2.UID, savedDashboard2.OrgID, true, PublicShareType) insertPublicDashboard(t, publicdashboardStore, savedDashboard3.UID, savedDashboard3.OrgID, true, EmailShareType) @@ -846,11 +846,10 @@ func TestGetMetrics(t *testing.T) { // helper function to insert a dashboard func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgID int64, - folderID int64, folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard { + folderUID string, isFolder bool, tags ...any) *dashboards.Dashboard { t.Helper() cmd := dashboards.SaveDashboardCommand{ OrgID: orgID, - FolderID: folderID, // nolint:staticcheck FolderUID: folderUID, IsFolder: isFolder, Dashboard: simplejson.NewFromAny(map[string]any{ diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index a21b109ccfa..3992d9f3b24 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -63,14 +63,6 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) { }, expectedResult: 110, }, - { - desc: "Should be able to view dashboards under the root with folders:uid:general scope", - permission: dashboardaccess.PERMISSION_VIEW, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, - }, - expectedResult: 10, - }, { desc: "Should not be able to view editable dashboards under the root with folders:uid:general scope if missing write action", permission: dashboardaccess.PERMISSION_EDIT, @@ -79,15 +71,6 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) { }, expectedResult: 0, }, - { - desc: "Should be able to view editable dashboards under the root with folders:uid:general scope if has write action", - permission: dashboardaccess.PERMISSION_EDIT, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, - {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, - }, - expectedResult: 10, - }, { desc: "Should be able to view a subset of dashboards with dashboard scopes", permission: dashboardaccess.PERMISSION_VIEW, @@ -101,15 +84,6 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) { }, expectedResult: 6, }, - { - desc: "Should be able to view a subset of dashboards with dashboard action and folder scope", - permission: dashboardaccess.PERMISSION_VIEW, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:8"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:10"}, - }, - expectedResult: 20, - }, { desc: "Should be able to view all folders with folder wildcard", permission: dashboardaccess.PERMISSION_VIEW, @@ -274,24 +248,6 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t }, expectedResult: 6, }, - { - desc: "Should be able to view a subset of dashboards with dashboard action and folder scope", - permission: dashboardaccess.PERMISSION_VIEW, - - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:8"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:10"}, - }, - expectedResult: 20, - }, - { - desc: "Should be able to view dashboards under the root with folders:uid:general scope", - permission: dashboardaccess.PERMISSION_VIEW, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, - }, - expectedResult: 10, - }, { desc: "Should not be able to view editable dashboards under the root with folders:uid:general scope if missing write action", permission: dashboardaccess.PERMISSION_EDIT, @@ -300,15 +256,6 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t }, expectedResult: 0, }, - { - desc: "Should be able to view editable dashboards under the root with folders:uid:general scope if has write action", - permission: dashboardaccess.PERMISSION_EDIT, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, - {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)}, - }, - expectedResult: 10, - }, { desc: "Should be able to view all folders with folder wildcard", permission: dashboardaccess.PERMISSION_VIEW, @@ -466,26 +413,6 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) { features: []any{featuremgmt.FlagNestedFolders}, expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"}, }, - { - desc: "Should be able to view dashboards under inherited folders if nested folders are enabled", - queryType: searchstore.TypeDashboard, - permission: dashboardaccess.PERMISSION_VIEW, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"}, - }, - { - desc: "Should not be able to view dashboards under inherited folders if nested folders are not enabled", - queryType: searchstore.TypeDashboard, - permission: dashboardaccess.PERMISSION_VIEW, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{}, - expectedResult: []string{"dashboard under parent folder"}, - }, { desc: "Should be able to view inherited folders if nested folders are enabled", queryType: searchstore.TypeFolder, @@ -506,26 +433,6 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) { features: []any{}, expectedResult: []string{"parent"}, }, - { - desc: "Should be able to view inherited dashboards and folders if nested folders are enabled", - permission: dashboardaccess.PERMISSION_VIEW, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"parent", "subfolder", "dashboard under parent folder", "dashboard under subfolder"}, - }, - { - desc: "Should not be able to view inherited dashboards and folders if nested folders are not enabled", - permission: dashboardaccess.PERMISSION_VIEW, - permissions: []accesscontrol.Permission{ - {Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{}, - expectedResult: []string{"parent", "dashboard under parent folder"}, - }, } origNewGuardian := guardian.New @@ -619,26 +526,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission features: []any{featuremgmt.FlagNestedFolders}, expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"}, }, - { - desc: "Should be able to view dashboards under inherited folders if nested folders are enabled", - queryType: searchstore.TypeDashboard, - permission: dashboardaccess.PERMISSION_VIEW, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"}, - }, - { - desc: "Should not be able to view dashboards under inherited folders if nested folders are not enabled", - queryType: searchstore.TypeDashboard, - permission: dashboardaccess.PERMISSION_VIEW, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{}, - expectedResult: []string{"dashboard under parent folder"}, - }, { desc: "Should be able to view inherited folders if nested folders are enabled", queryType: searchstore.TypeFolder, @@ -659,40 +546,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission features: []any{}, expectedResult: []string{"parent"}, }, - { - desc: "Should be able to view inherited dashboards and folders if nested folders are enabled", - permission: dashboardaccess.PERMISSION_VIEW, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"parent", "subfolder", "dashboard under parent folder", "dashboard under subfolder"}, - }, - { - desc: "Should not be able to view inherited dashboards and folders if nested folders are not enabled", - permission: dashboardaccess.PERMISSION_VIEW, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - }, - features: []any{}, - expectedResult: []string{"parent", "dashboard under parent folder"}, - }, - { - desc: "Should be able to edit inherited dashboards and folders if nested folders are enabled", - permission: dashboardaccess.PERMISSION_EDIT, - signedInUserPermissions: []accesscontrol.Permission{ - {Action: dashboards.ActionFoldersRead, Scope: "folders:uid:subfolder"}, - {Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:subfolder"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:subfolder"}, - {Action: dashboards.ActionDashboardsWrite, Scope: "folders:uid:subfolder"}, - {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"}, - {Action: dashboards.ActionDashboardsWrite, Scope: "folders:uid:parent"}, - }, - features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"subfolder", "dashboard under parent folder", "dashboard under subfolder"}, - }, } origNewGuardian := guardian.New @@ -776,15 +629,15 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access folderID = i % (numFolders + 1) } dashes = append(dashes, dashboards.Dashboard{ - OrgID: 1, - IsFolder: false, - FolderID: int64(folderID), // nolint:staticcheck - UID: str, - Slug: str, - Title: str, - Data: simplejson.New(), - Created: time.Now(), - Updated: time.Now(), + OrgID: 1, + IsFolder: false, + FolderUID: strconv.Itoa(folderID), + UID: str, + Slug: str, + Title: str, + Data: simplejson.New(), + Created: time.Now(), + Updated: time.Now(), }) } @@ -867,7 +720,6 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol // create dashboard under parent folder _, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{ OrgID: orgID, - FolderID: parent.ID, // nolint:staticcheck FolderUID: parent.UID, Dashboard: simplejson.NewFromAny(map[string]any{ "title": "dashboard under parent folder", @@ -878,7 +730,6 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol // create dashboard under subfolder _, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{ OrgID: orgID, - FolderID: subfolder.ID, // nolint:staticcheck FolderUID: subfolder.UID, Dashboard: simplejson.NewFromAny(map[string]any{ "title": "dashboard under subfolder", From 2c4e1d4baf05795bcdd209ceccb0d9cc862af7a4 Mon Sep 17 00:00:00 2001 From: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:39:21 +0100 Subject: [PATCH 036/169] Tempo: Fix typo (#81383) --- public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx index 5d5e001395c..253a6240870 100644 --- a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx +++ b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx @@ -22,7 +22,7 @@ interface Props { } export function TraceQLEditor(props: Props) { - const [alertText, setAlertText] = useState('error error'); + const [alertText, setAlertText] = useState(''); const { onChange, onRunQuery, placeholder } = props; const setupAutocompleteFn = useAutocomplete(props.datasource, setAlertText); From cca3bb09173c085026106a732230309f02745e7e Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 26 Jan 2024 15:48:06 +0000 Subject: [PATCH 037/169] Chore: Expose icons and add icon documentation (#81371) * expose icons and initial draft documentation * expose archive-alt not archive * doc tweak * update docs * remove some width/heights from icons --- packages/grafana-data/src/types/icon.ts | 3 +++ packages/grafana-ui/src/components/Icon/utils.ts | 4 ++-- public/img/icons/README.md | 12 ++++++++++++ public/img/icons/unicons/ai.svg | 2 +- .../img/icons/unicons/application-observability.svg | 4 ++-- public/img/icons/unicons/frontend-observability.svg | 2 +- public/img/icons/unicons/k6.svg | 8 ++++---- public/img/icons/unicons/spinner.svg | 2 +- public/img/icons/unicons/unarchive.svg | 4 ++++ 9 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 public/img/icons/README.md create mode 100644 public/img/icons/unicons/unarchive.svg diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 019ce607583..e39648bf1a7 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -23,6 +23,7 @@ export const availableIconsIndex = { 'align-right': true, 'application-observability': true, apps: true, + 'archive-alt': true, arrow: true, 'arrow-down': true, 'arrow-from-right': true, @@ -135,6 +136,7 @@ export const availableIconsIndex = { 'gf-pin': true, 'gf-prometheus': true, 'gf-traces': true, + globe: true, grafana: true, 'graph-bar': true, heart: true, @@ -223,6 +225,7 @@ export const availableIconsIndex = { 'toggle-on': true, 'toggle-off': true, 'trash-alt': true, + unarchive: true, unlock: true, upload: true, user: true, diff --git a/packages/grafana-ui/src/components/Icon/utils.ts b/packages/grafana-ui/src/components/Icon/utils.ts index 0d379f36c7a..229aa5361e0 100644 --- a/packages/grafana-ui/src/components/Icon/utils.ts +++ b/packages/grafana-ui/src/components/Icon/utils.ts @@ -1,4 +1,4 @@ -import { IconName, IconSize } from '../../types/icon'; +import { IconName, IconSize, IconType } from '../../types/icon'; const alwaysMonoIcons: IconName[] = [ 'grafana', @@ -10,7 +10,7 @@ const alwaysMonoIcons: IconName[] = [ 'circle-mono', ]; -export function getIconSubDir(name: IconName, type: string): string { +export function getIconSubDir(name: IconName, type: IconType): string { if (name?.startsWith('gf-')) { return 'custom'; } else if (alwaysMonoIcons.includes(name)) { diff --git a/public/img/icons/README.md b/public/img/icons/README.md new file mode 100644 index 00000000000..88acd06c678 --- /dev/null +++ b/public/img/icons/README.md @@ -0,0 +1,12 @@ +# How to add a new icon + +- Add the new icon svg to the `unicons/` directory + - Yes, even if it's not from [IconScout](https://iconscout.com/unicons/solid-icons) + - We will eventually condense all the separate folders into a single `icons/` directory, and since `unicons/` is the default it makes sense to add new icons there +- Ensure the new icon source is formatted correctly: + - Remove any `width` or `height` attributes + - If the icon is a single color, ensure any explicitly defined fill or stroke colors are either removed or set to `currentColor` + - This allows the consumer to control the color of the `Icon`, which is useful for hover/focus states +- Modify the [`availableIconsIndex` map in `@grafana/data`](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/icon.ts#L1) and add the new icon + - **Note:** the key must exactly match the icon filename, e.g. if your new icon is `my-icon.svg`, the key must be `my-icon` +- Run `yarn storybook` and verify the new icon appears correctly in the `Icon` story diff --git a/public/img/icons/unicons/ai.svg b/public/img/icons/unicons/ai.svg index d8df892aa90..0ea0f2502f7 100644 --- a/public/img/icons/unicons/ai.svg +++ b/public/img/icons/unicons/ai.svg @@ -1,4 +1,4 @@ - + diff --git a/public/img/icons/unicons/application-observability.svg b/public/img/icons/unicons/application-observability.svg index 9ea3ec93680..cc33bd99ddf 100644 --- a/public/img/icons/unicons/application-observability.svg +++ b/public/img/icons/unicons/application-observability.svg @@ -1,9 +1,9 @@ - + - + diff --git a/public/img/icons/unicons/frontend-observability.svg b/public/img/icons/unicons/frontend-observability.svg index 743b87dc8a8..38ffbd70502 100644 --- a/public/img/icons/unicons/frontend-observability.svg +++ b/public/img/icons/unicons/frontend-observability.svg @@ -1,4 +1,4 @@ - + diff --git a/public/img/icons/unicons/k6.svg b/public/img/icons/unicons/k6.svg index 26107f6960d..ba5cd2d7e39 100644 --- a/public/img/icons/unicons/k6.svg +++ b/public/img/icons/unicons/k6.svg @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/public/img/icons/unicons/spinner.svg b/public/img/icons/unicons/spinner.svg index fca18c39393..62eb684eb27 100644 --- a/public/img/icons/unicons/spinner.svg +++ b/public/img/icons/unicons/spinner.svg @@ -1,3 +1,3 @@ - + diff --git a/public/img/icons/unicons/unarchive.svg b/public/img/icons/unicons/unarchive.svg new file mode 100644 index 00000000000..afe7b426b38 --- /dev/null +++ b/public/img/icons/unicons/unarchive.svg @@ -0,0 +1,4 @@ + + + + From 8ca080d47c96b918a402034a2dbc74e8907e41d8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:48:40 +0000 Subject: [PATCH 038/169] Update dependency react-router-dom-v5-compat to v6.21.3 (#81379) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index c138a4dfaaa..b9fc1883a76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6365,13 +6365,6 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.5.0": - version: 1.5.0 - resolution: "@remix-run/router@npm:1.5.0" - checksum: 3da27d64519df94919020f4b42aaa016f26891331be98468cbc807bc4d2cfb401d7e47d4f88a4a3d777fc3af23d162c668357a8e5d2c5947acdbca7b691bc325 - languageName: node - linkType: hard - "@rollup/plugin-commonjs@npm:25.0.7": version: 25.0.7 resolution: "@rollup/plugin-commonjs@npm:25.0.7" @@ -25416,16 +25409,16 @@ __metadata: linkType: hard "react-router-dom-v5-compat@npm:^6.10.0": - version: 6.10.0 - resolution: "react-router-dom-v5-compat@npm:6.10.0" + version: 6.21.3 + resolution: "react-router-dom-v5-compat@npm:6.21.3" dependencies: history: "npm:^5.3.0" - react-router: "npm:6.10.0" + react-router: "npm:6.21.3" peerDependencies: react: ">=16.8" react-dom: ">=16.8" react-router-dom: 4 || 5 - checksum: b86edb22640e25687a843fd46c22b58e147316ee957653e684d9f46a7e71da113a761a2f74c60d42f5d4222e497f825e541a1f4d8872b26edb4b623fb7c87928 + checksum: ed40e3e241a8c2a85aa0a95b52b77b9b9667e3f1a48a8a60e0b33e3c615c3b490b6c3a645ea92efc5783251111d039a5c809b779e1a93b9da23de6e97e4e1bd0 languageName: node linkType: hard @@ -25466,17 +25459,6 @@ __metadata: languageName: node linkType: hard -"react-router@npm:6.10.0": - version: 6.10.0 - resolution: "react-router@npm:6.10.0" - dependencies: - "@remix-run/router": "npm:1.5.0" - peerDependencies: - react: ">=16.8" - checksum: 3c78db213d2c67c7ae06125b296889ebf3407963268ef23319312b4d7bf455ecfaa59164be73d6b4e19fb2ef6c2771d7dfe764d5a91cbdbb7c8e84c95aca99cc - languageName: node - linkType: hard - "react-router@npm:6.21.3": version: 6.21.3 resolution: "react-router@npm:6.21.3" From 09fcb3c6cce9ed1743670b4c0a724a74cb4a9716 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:02:44 -0600 Subject: [PATCH 039/169] Candlestick: Add tooltip options (#81307) --- packages/grafana-ui/src/options/builder/tooltip.tsx | 7 ++++--- .../app/plugins/panel/candlestick/CandlestickPanel.tsx | 10 ++++++---- public/app/plugins/panel/candlestick/module.tsx | 5 ++++- public/app/plugins/panel/candlestick/types.ts | 6 +++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/grafana-ui/src/options/builder/tooltip.tsx b/packages/grafana-ui/src/options/builder/tooltip.tsx index 72af8574d68..b342103efe2 100644 --- a/packages/grafana-ui/src/options/builder/tooltip.tsx +++ b/packages/grafana-ui/src/options/builder/tooltip.tsx @@ -3,7 +3,8 @@ import { OptionsWithTooltip, TooltipDisplayMode, SortOrder } from '@grafana/sche export function addTooltipOptions( builder: PanelOptionsEditorBuilder, - singleOnly = false + singleOnly = false, + defaultOptions?: Partial ) { const category = ['Tooltip']; const modeOptions = singleOnly @@ -28,7 +29,7 @@ export function addTooltipOptions( path: 'tooltip.mode', name: 'Tooltip mode', category, - defaultValue: 'single', + defaultValue: defaultOptions?.tooltip?.mode ?? TooltipDisplayMode.Single, settings: { options: modeOptions, }, @@ -37,7 +38,7 @@ export function addTooltipOptions( path: 'tooltip.sort', name: 'Values sort order', category, - defaultValue: SortOrder.None, + defaultValue: defaultOptions?.tooltip?.sort ?? SortOrder.None, showIf: (options: T) => options.tooltip?.mode === TooltipDisplayMode.Multi, settings: { options: sortOptions, diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index eabc0242c1b..57bc0b92e2f 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -266,7 +266,9 @@ export const CandlestickPanel = ({ {showNewVizTooltips ? ( { @@ -293,14 +295,14 @@ export const CandlestickPanel = ({ seriesFrame={alignedDataFrame} dataIdxs={dataIdxs} seriesIdx={seriesIdx} - mode={TooltipDisplayMode.Multi} + mode={options.tooltip.mode} isPinned={isPinned} annotate={enableAnnotationCreation ? annotate : undefined} /> ); }} - maxWidth={options.tooltip?.maxWidth} - maxHeight={options.tooltip?.maxHeight} + maxWidth={options.tooltip.maxWidth} + maxHeight={options.tooltip.maxHeight} /> ) : ( <> diff --git a/public/app/plugins/panel/candlestick/module.tsx b/public/app/plugins/panel/candlestick/module.tsx index d4ae7b534a9..0174594015b 100644 --- a/public/app/plugins/panel/candlestick/module.tsx +++ b/public/app/plugins/panel/candlestick/module.tsx @@ -137,7 +137,10 @@ export const plugin = new PanelPlugin(CandlestickPane }, }); - // commonOptionsBuilder.addTooltipOptions(builder); + if (config.featureToggles.newVizTooltips) { + commonOptionsBuilder.addTooltipOptions(builder, false, opts); + } + commonOptionsBuilder.addLegendOptions(builder); }) .setDataSupport({ annotations: true, alertStates: true }) diff --git a/public/app/plugins/panel/candlestick/types.ts b/public/app/plugins/panel/candlestick/types.ts index 81253afa1a5..adf699253c8 100644 --- a/public/app/plugins/panel/candlestick/types.ts +++ b/public/app/plugins/panel/candlestick/types.ts @@ -1,4 +1,4 @@ -import { LegendDisplayMode } from '@grafana/schema'; +import { LegendDisplayMode, SortOrder, TooltipDisplayMode } from '@grafana/schema'; import { defaultOptions as defaultOptionsBase, @@ -21,6 +21,10 @@ export const defaultOptions: Partial = { placement: 'bottom', calcs: [], }, + tooltip: { + mode: TooltipDisplayMode.Multi, + sort: SortOrder.None, + }, }; export { From a081abdd258cc48ffc34956603ef974f802d3e48 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Fri, 26 Jan 2024 12:12:45 -0500 Subject: [PATCH 040/169] Folders: GetFolders to return empty respons if user does not have any permissions (#81304) add check for list of permissions --- pkg/services/folder/folderimpl/folder.go | 3 +++ pkg/services/folder/folderimpl/folder_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 8db17f447ca..7fb2488675d 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -131,6 +131,9 @@ func (s *Service) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]* permissions := q.SignedInUser.GetPermissions() folderPermissions := permissions[dashboards.ActionFoldersRead] qry.ancestorUIDs = make([]string, 0, len(folderPermissions)) + if len(folderPermissions) == 0 && !q.SignedInUser.GetIsGrafanaAdmin() { + return nil, nil + } for _, p := range folderPermissions { if p == dashboards.ScopeFoldersAll { // no need to query for folders with permissions diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 8798658ff9e..c35cc457500 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -1309,6 +1309,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { orgID: { dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}, + dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}, }, }} @@ -1587,6 +1588,16 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { }, }, }, + { + name: "Should not get any folders if user has no permissions", + cmd: folder.GetFoldersQuery{ + OrgID: orgID, + SignedInUser: &user.SignedInUser{UserID: 999, OrgID: orgID, Permissions: map[int64]map[string][]string{ + orgID: {}, + }}, + }, + expected: nil, + }, } for _, tc := range testCases { From 138079bbd80be5188e039463f7e5377507ac698b Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Fri, 26 Jan 2024 10:14:59 -0700 Subject: [PATCH 041/169] Transformations: Fix regression where disabling single transformation would display "No data" (#81320) --- e2e/panels-suite/panelEdit_transforms.spec.ts | 13 ++++++++++++- .../src/selectors/components.ts | 2 ++ .../QueryOperationRow/QueryOperationAction.tsx | 2 ++ .../TransformationOperationRow.tsx | 2 ++ .../panel/components/PanelDataErrorView.tsx | 5 ++++- public/app/features/query/state/PanelQueryRunner.ts | 3 ++- 6 files changed, 24 insertions(+), 3 deletions(-) diff --git a/e2e/panels-suite/panelEdit_transforms.spec.ts b/e2e/panels-suite/panelEdit_transforms.spec.ts index c71f649791c..eb3029b8d98 100644 --- a/e2e/panels-suite/panelEdit_transforms.spec.ts +++ b/e2e/panels-suite/panelEdit_transforms.spec.ts @@ -6,7 +6,7 @@ describe('Panel edit tests - transformations', () => { }); it('Tests transformations editor', () => { - e2e.flows.openDashboard({ uid: '5SdHCadmz', queryParams: { editPanel: 3 } }); + e2e.flows.openDashboard({ uid: 'TkZXxlNG3', queryParams: { editPanel: 47 } }); e2e.components.Tab.title('Transform data').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -14,4 +14,15 @@ describe('Panel edit tests - transformations', () => { e2e.components.Transforms.Reduce.calculationsLabel().scrollIntoView().should('be.visible'); e2e.components.Transforms.Reduce.modeLabel().should('be.visible'); }); + + it('Tests case where transformations can be disabled and not clear out panel data', () => { + e2e.flows.openDashboard({ uid: 'TkZXxlNG3', queryParams: { editPanel: 47 } }); + + e2e.components.Tab.title('Transform data').should('be.visible').click(); + e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); + e2e.components.TransformTab.newTransform('Reduce').scrollIntoView().should('be.visible').click(); + e2e.components.Transforms.disableTransformationButton().should('be.visible').click(); + + e2e.components.Panels.Panel.PanelDataErrorMessage().should('not.exist'); + }); }); diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 5a772cab53a..c9dde2bacaa 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -164,6 +164,7 @@ export const Components = { container: 'data-testid hover-header-container', dragIcon: 'data-testid drag-icon', }, + PanelDataErrorMessage: 'data-testid Panel data error message', }, Visualization: { Graph: { @@ -306,6 +307,7 @@ export const Components = { }, Transforms: { card: (name: string) => `data-testid New transform ${name}`, + disableTransformationButton: 'data-testid Disable transformation button', Reduce: { modeLabel: 'data-testid Transform mode label', calculationsLabel: 'data-testid Transform calculations label', diff --git a/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx b/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx index fe69509ac4a..0dc58eff83c 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx @@ -10,6 +10,7 @@ interface BaseQueryOperationActionProps { title: string; onClick: (e: React.MouseEvent) => void; disabled?: boolean; + dataTestId?: string; } function BaseQueryOperationAction(props: QueryOperationActionProps | QueryOperationToggleActionProps) { @@ -25,6 +26,7 @@ function BaseQueryOperationAction(props: QueryOperationActionProps | QueryOperat onClick={props.onClick} type="button" aria-label={selectors.components.QueryEditorRow.actionButton(props.title)} + data-testid={props.dataTestId} {...('active' in props && { 'aria-pressed': props.active })} />
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx index 59fb8ad76e2..aa5f6df86f5 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx @@ -2,6 +2,7 @@ import React, { useCallback } from 'react'; import { useToggle } from 'react-use'; import { DataTransformerConfig, TransformerRegistryItem, FrameMatcherID, DataTopic } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; import { ConfirmModal } from '@grafana/ui'; import { @@ -120,6 +121,7 @@ export const TransformationOperationRow = ({ icon={disabled ? 'eye-slash' : 'eye'} onClick={instrumentToggleCallback(() => onDisableToggle(index), 'disabled', disabled)} active={disabled} + dataTestId={selectors.components.Transforms.disableTransformationButton} /> -
{message}
+
+ {message} +
{context.app === CoreApp.PanelEditor && dataSummary.hasData && panel && (
{props.suggestions && ( diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index ed1f6fcb1e3..6ff46053ef8 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -219,7 +219,8 @@ export class PanelQueryRunner { private applyTransformations(data: PanelData): Observable { const transformations = this.dataConfigSource.getTransformations(); - if (!transformations || transformations.length === 0) { + const allTransformationsDisabled = transformations && transformations.every((t) => t.disabled); + if (allTransformationsDisabled || !transformations || transformations.length === 0) { return of(data); } From 048d1e7c861c419c7b2522429b654cc17a9c903b Mon Sep 17 00:00:00 2001 From: Ieva Date: Fri, 26 Jan 2024 17:17:29 +0000 Subject: [PATCH 042/169] RBAC: Annotation permission migration (#78899) * add annotation permissions to dashboard managed role and add migrations for annotation permissions * fix a bug with conditional access level definitions * add tests * Update pkg/services/sqlstore/migrations/accesscontrol/dashboard_permissions.go Co-authored-by: Gabriel MABILLE * apply feedback * add batching, fix tests and a typo * add one more test * undo unneeded change * undo unwanted change * only check the default basic permissions for non-OSS instances * account for all wildcards and simplify the check a bit * error handling and extra conditionals to avoid test failures * fix a bug with admin permissions not appearing for folders * fix the OSS check --------- Co-authored-by: Gabriel MABILLE --- pkg/api/accesscontrol.go | 8 +- .../ossaccesscontrol/permissions_services.go | 33 +- .../accesscontrol/dashboard_permissions.go | 165 +++++++++ .../migrations/accesscontrol/test/ac_test.go | 3 + .../test/dashbord_permission_migrator_test.go | 335 ++++++++++++++++++ .../test/managed_permission_migrator_test.go | 6 +- .../sqlstore/migrations/migrations.go | 9 + 7 files changed, 547 insertions(+), 12 deletions(-) create mode 100644 pkg/services/sqlstore/migrations/accesscontrol/test/dashbord_permission_migrator_test.go diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index f779facc640..cd0d92fda1b 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -606,7 +606,7 @@ func (hs *HTTPServer) declareFixedRoles() error { Group: "Annotations", Permissions: []ac.Permission{ {Action: ac.ActionAnnotationsRead, Scope: ac.ScopeAnnotationsTypeOrganization}, - {Action: ac.ActionAnnotationsRead, Scope: dashboards.ScopeDashboardsAll}, + {Action: ac.ActionAnnotationsRead, Scope: dashboards.ScopeFoldersAll}, }, }, Grants: []string{string(org.RoleAdmin)}, @@ -620,11 +620,11 @@ func (hs *HTTPServer) declareFixedRoles() error { Group: "Annotations", Permissions: []ac.Permission{ {Action: ac.ActionAnnotationsCreate, Scope: ac.ScopeAnnotationsTypeOrganization}, - {Action: ac.ActionAnnotationsCreate, Scope: dashboards.ScopeDashboardsAll}, + {Action: ac.ActionAnnotationsCreate, Scope: dashboards.ScopeFoldersAll}, {Action: ac.ActionAnnotationsDelete, Scope: ac.ScopeAnnotationsTypeOrganization}, - {Action: ac.ActionAnnotationsDelete, Scope: dashboards.ScopeDashboardsAll}, + {Action: ac.ActionAnnotationsDelete, Scope: dashboards.ScopeFoldersAll}, {Action: ac.ActionAnnotationsWrite, Scope: ac.ScopeAnnotationsTypeOrganization}, - {Action: ac.ActionAnnotationsWrite, Scope: dashboards.ScopeDashboardsAll}, + {Action: ac.ActionAnnotationsWrite, Scope: dashboards.ScopeFoldersAll}, }, }, Grants: []string{string(org.RoleAdmin)}, diff --git a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go index d594a8bbe67..1645bc09836 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go @@ -118,6 +118,27 @@ var DashboardViewActions = []string{dashboards.ActionDashboardsRead} var DashboardEditActions = append(DashboardViewActions, []string{dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsDelete}...) var DashboardAdminActions = append(DashboardEditActions, []string{dashboards.ActionDashboardsPermissionsRead, dashboards.ActionDashboardsPermissionsWrite}...) +func getDashboardViewActions(features featuremgmt.FeatureToggles) []string { + if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) { + return append(DashboardViewActions, accesscontrol.ActionAnnotationsRead) + } + return DashboardViewActions +} + +func getDashboardEditActions(features featuremgmt.FeatureToggles) []string { + if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) { + return append(DashboardEditActions, []string{accesscontrol.ActionAnnotationsRead, accesscontrol.ActionAnnotationsWrite, accesscontrol.ActionAnnotationsDelete, accesscontrol.ActionAnnotationsCreate}...) + } + return DashboardEditActions +} + +func getDashboardAdminActions(features featuremgmt.FeatureToggles) []string { + if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) { + return append(DashboardAdminActions, []string{accesscontrol.ActionAnnotationsRead, accesscontrol.ActionAnnotationsWrite, accesscontrol.ActionAnnotationsDelete, accesscontrol.ActionAnnotationsCreate}...) + } + return DashboardAdminActions +} + func ProvideDashboardPermissions( cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl, license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service, @@ -177,9 +198,9 @@ func ProvideDashboardPermissions( ServiceAccounts: true, }, PermissionsToActions: map[string][]string{ - "View": DashboardViewActions, - "Edit": DashboardEditActions, - "Admin": DashboardAdminActions, + "View": getDashboardViewActions(features), + "Edit": getDashboardEditActions(features), + "Admin": getDashboardAdminActions(features), }, ReaderRoleName: "Dashboard permission reader", WriterRoleName: "Dashboard permission writer", @@ -242,9 +263,9 @@ func ProvideFolderPermissions( ServiceAccounts: true, }, PermissionsToActions: map[string][]string{ - "View": append(DashboardViewActions, FolderViewActions...), - "Edit": append(DashboardEditActions, FolderEditActions...), - "Admin": append(DashboardAdminActions, FolderAdminActions...), + "View": append(getDashboardViewActions(features), FolderViewActions...), + "Edit": append(getDashboardEditActions(features), FolderEditActions...), + "Admin": append(getDashboardAdminActions(features), FolderAdminActions...), }, ReaderRoleName: "Folder permission reader", WriterRoleName: "Folder permission writer", diff --git a/pkg/services/sqlstore/migrations/accesscontrol/dashboard_permissions.go b/pkg/services/sqlstore/migrations/accesscontrol/dashboard_permissions.go index 91bec67adba..0198ded5738 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/dashboard_permissions.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/dashboard_permissions.go @@ -661,6 +661,171 @@ func (m *managedFolderLibraryPanelActionsMigrator) Exec(sess *xorm.Session, mg * return nil } +const ManagedDashboardAnnotationActionsMigratorID = "managed dashboard permissions annotation actions migration" + +func AddManagedDashboardAnnotationActionsMigration(mg *migrator.Migrator) { + mg.AddMigration(ManagedDashboardAnnotationActionsMigratorID, &managedDashboardAnnotationActionsMigrator{}) +} + +type managedDashboardAnnotationActionsMigrator struct { + migrator.MigrationBase +} + +func (m *managedDashboardAnnotationActionsMigrator) SQL(dialect migrator.Dialect) string { + return CodeMigrationSQL +} + +func (m *managedDashboardAnnotationActionsMigrator) Exec(sess *xorm.Session, mg *migrator.Migrator) error { + // Check if roles have been populated and return early if they haven't - this avoids logging a warning from hasDefaultAnnotationPermissions + roleCount := 0 + _, err := sess.SQL(`SELECT COUNT( DISTINCT r.uid ) FROM role AS r INNER JOIN permission AS p ON r.id = p.role_id WHERE r.uid IN (?, ?, ?)`, "basic_viewer", "basic_editor", "basic_admin").Get(&roleCount) + if err != nil { + return fmt.Errorf("failed to check if basic roles have been populated: %w", err) + } + // Role count will be 0 either for new Grafana installations (in that case no managed roles will exist either, and the next conditional will return nil) + // or for OSS instances, for which basic role permissions can't be changed, so we don't need to run the default permission check in that case. + if roleCount != 0 { + // Check that default annotation permissions are assigned to basic roles. If that is not the case, skip the migration. + if hasDefaultPerms, err := m.hasDefaultAnnotationPermissions(sess, mg); err != nil || !hasDefaultPerms { + return err + } + } + + var ids []any + if err := sess.SQL("SELECT id FROM role WHERE name LIKE 'managed:%'").Find(&ids); err != nil { + return err + } + + if len(ids) == 0 { + return nil + } + + var permissions []ac.Permission + roleQueryBatchSize := 100 + err = batch(len(ids), roleQueryBatchSize, func(start, end int) error { + var batchPermissions []ac.Permission + if err := sess.SQL("SELECT role_id, action, scope FROM permission WHERE role_id IN(?"+strings.Repeat(" ,?", len(ids[start:end])-1)+") AND (scope LIKE 'folders:%' or scope LIKE 'dashboards:%')", ids[start:end]...).Find(&batchPermissions); err != nil { + return err + } + permissions = append(permissions, batchPermissions...) + return nil + }) + if err != nil { + return err + } + + mapped := make(map[int64]map[string]map[string]bool, len(ids)-1) + for _, p := range permissions { + if mapped[p.RoleID] == nil { + mapped[p.RoleID] = make(map[string]map[string]bool) + } + if mapped[p.RoleID][p.Scope] == nil { + mapped[p.RoleID][p.Scope] = make(map[string]bool) + } + mapped[p.RoleID][p.Scope][p.Action] = true + } + + var toAdd []ac.Permission + now := time.Now() + + for roleId, mappedPermissions := range mapped { + for scope, roleActions := range mappedPermissions { + if roleActions[dashboards.ActionDashboardsRead] { + if !roleActions[ac.ActionAnnotationsRead] { + toAdd = append(toAdd, ac.Permission{ + RoleID: roleId, + Updated: now, + Created: now, + Scope: scope, + Action: ac.ActionAnnotationsRead, + }) + } + } + + if roleActions[dashboards.ActionDashboardsWrite] { + if !roleActions[ac.ActionAnnotationsCreate] { + toAdd = append(toAdd, ac.Permission{ + RoleID: roleId, + Updated: now, + Created: now, + Scope: scope, + Action: ac.ActionAnnotationsCreate, + }) + } + if !roleActions[ac.ActionAnnotationsDelete] { + toAdd = append(toAdd, ac.Permission{ + RoleID: roleId, + Updated: now, + Created: now, + Scope: scope, + Action: ac.ActionAnnotationsDelete, + }) + } + if !roleActions[ac.ActionAnnotationsWrite] { + toAdd = append(toAdd, ac.Permission{ + RoleID: roleId, + Updated: now, + Created: now, + Scope: scope, + Action: ac.ActionAnnotationsWrite, + }) + } + } + } + } + + if len(toAdd) == 0 { + return nil + } + + return batch(len(toAdd), batchSize, func(start, end int) error { + _, err := sess.InsertMulti(toAdd[start:end]) + return err + }) +} + +func (m *managedDashboardAnnotationActionsMigrator) hasDefaultAnnotationPermissions(sess *xorm.Session, mg *migrator.Migrator) (bool, error) { + type basicRolePermission struct { + Uid string + Action string + Scope string + } + + var basicRolePermissions []basicRolePermission + basicRoleUIDs := []any{"basic_viewer", "basic_editor", "basic_admin"} + query := `SELECT r.uid, p.action, p.scope FROM role r +LEFT OUTER JOIN permission p ON p.role_id = r.id +WHERE r.uid IN (?, ?, ?) AND p.action LIKE 'annotations:%' AND p.scope IN ('*', 'annotations:*', 'annotations:type:*', 'annotations:type:dashboard') +` + if err := sess.SQL(query, basicRoleUIDs...).Find(&basicRolePermissions); err != nil { + return false, fmt.Errorf("failed to list basic role permissions: %w", err) + } + + mappedBasicRolePerms := make(map[string]map[string]bool, 0) + for _, p := range basicRolePermissions { + if mappedBasicRolePerms[p.Uid] == nil { + mappedBasicRolePerms[p.Uid] = make(map[string]bool) + } + mappedBasicRolePerms[p.Uid][p.Action] = true + } + + expectedAnnotationActions := []string{ac.ActionAnnotationsRead, ac.ActionAnnotationsCreate, ac.ActionAnnotationsDelete, ac.ActionAnnotationsWrite} + + for _, uid := range basicRoleUIDs { + if mappedBasicRolePerms[uid.(string)] == nil { + mg.Logger.Warn("basic role permissions missing annotation permissions, skipping annotation permission migration", "uid", uid) + return false, nil + } + for _, action := range expectedAnnotationActions { + if !mappedBasicRolePerms[uid.(string)][action] { + mg.Logger.Warn("basic role permissions missing annotation permissions, skipping annotation permission migration", "uid", uid, "action", action) + return false, nil + } + } + } + return true, nil +} + func hasFolderAdmin(permissions []ac.Permission) bool { return hasActions(folderPermissionTranslation[dashboardaccess.PERMISSION_ADMIN], permissions) } diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index d02b401d2f6..7e7f74f1666 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -262,6 +262,9 @@ func setupTestDB(t *testing.T) *xorm.Engine { mg := migrator.NewMigrator(x, &setting.Cfg{ Logger: log.New("acmigration.test"), Raw: ini.Empty(), + IsFeatureToggleEnabled: func(key string) bool { + return true + }, }) migrations := &migrations.OSSMigrations{} migrations.AddMigration(mg) diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/dashbord_permission_migrator_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/dashbord_permission_migrator_test.go new file mode 100644 index 00000000000..935977ebc59 --- /dev/null +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/dashbord_permission_migrator_test.go @@ -0,0 +1,335 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/dashboards" + acmig "github.com/grafana/grafana/pkg/services/sqlstore/migrations/accesscontrol" + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/setting" +) + +type testCase struct { + desc string + putRolePerms map[int64]map[string][]rawPermission + wantRolePerms map[int64]map[string][]rawPermission +} + +func testCases() []testCase { + allAnnotationPermissions := []rawPermission{ + {Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + {Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}, + } + + onlyOrgAnnotations := []rawPermission{ + {Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}, + {Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}, + {Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}, + {Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}, + } + + wildcardAnnotationPermissions := []rawPermission{ + {Action: accesscontrol.ActionAnnotationsRead, Scope: "*"}, + {Action: accesscontrol.ActionAnnotationsCreate, Scope: "annotations:*"}, + {Action: accesscontrol.ActionAnnotationsDelete, Scope: "annotations:type:*"}, + {Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsAll}, + } + + return []testCase{ + { + desc: "empty permissions lead to empty permissions", + putRolePerms: map[int64]map[string][]rawPermission{}, + wantRolePerms: map[int64]map[string][]rawPermission{}, + }, + { + desc: "adds new permissions for instances without basic roles (should only be OSS instances)", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "managed:users:1:permissions": {{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}}, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "dashboards:uid:test"}, + }, + }, + }, + }, + { + desc: "doesn't add any new permissions if has default annotation permissions on basic roles but no dashboard or folder permissions", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + }, + }, + }, + { + desc: "adds new permissions if has default annotation permissions on basic roles and dashboard read permissions", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": {{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}}, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "dashboards:uid:test"}, + }, + }, + }, + }, + { + desc: "adds new permissions if has default annotation permissions on basic roles and dashboard write permissions", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:test"}, + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + }, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:test"}, + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsWrite, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsDelete, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsCreate, Scope: "dashboards:uid:test"}, + }, + }, + }, + }, + { + desc: "adds new permissions if has default annotation permissions on basic roles and folder read permissions", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": {{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:test"}}, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "folders:uid:test"}, + }, + }, + }, + }, + { + desc: "adds new permissions if has default annotation permissions on basic roles and folder write permissions", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsWrite, Scope: "folders:uid:test"}, + {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:test"}, + }, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsWrite, Scope: "folders:uid:test"}, + {Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "folders:uid:test"}, + {Action: accesscontrol.ActionAnnotationsWrite, Scope: "folders:uid:test"}, + {Action: accesscontrol.ActionAnnotationsDelete, Scope: "folders:uid:test"}, + {Action: accesscontrol.ActionAnnotationsCreate, Scope: "folders:uid:test"}, + }, + }, + }, + }, + { + desc: "adds new permissions to several managed roles if has default annotation permissions on basic roles and dashboard read permissions", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": {{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}}, + "managed:teams:1:permissions": {{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test2"}}, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": allAnnotationPermissions, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "dashboards:uid:test"}, + }, + "managed:teams:1:permissions": { + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test2"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "dashboards:uid:test2"}, + }, + }, + }, + }, + { + desc: "doesn't add any new permissions if annotation permissions are missing from the basic roles", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:test"}, + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + }, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + }, + }, + }, + { + desc: "doesn't add any new permissions if annotation permissions from the basic roles don't have the dashboard scope", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": onlyOrgAnnotations, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsWrite, Scope: "dashboards:uid:test"}, + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + }, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": onlyOrgAnnotations, + "basic:editor": allAnnotationPermissions, + "basic:admin": allAnnotationPermissions, + }, + }, + }, + { + desc: "adds new permissions if has default annotation permissions with different wildcard scopes", + putRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": wildcardAnnotationPermissions, + "basic:editor": wildcardAnnotationPermissions, + "basic:admin": wildcardAnnotationPermissions, + "managed:users:1:permissions": {{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}}, + }, + }, + wantRolePerms: map[int64]map[string][]rawPermission{ + 1: { + "basic:viewer": wildcardAnnotationPermissions, + "basic:editor": wildcardAnnotationPermissions, + "basic:admin": wildcardAnnotationPermissions, + "managed:users:1:permissions": { + {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:test"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "dashboards:uid:test"}, + }, + }, + }, + }, + } +} + +func TestAnnotationActionMigration(t *testing.T) { + // Run initial migration to have a working DB + x := setupTestDB(t) + + for _, tc := range testCases() { + t.Run(tc.desc, func(t *testing.T) { + // Remove migration + _, errDeleteMig := x.Exec(`DELETE FROM migration_log WHERE migration_id LIKE ?`, acmig.ManagedDashboardAnnotationActionsMigratorID) + require.NoError(t, errDeleteMig) + _, errDeletePerm := x.Exec(`DELETE FROM permission`) + require.NoError(t, errDeletePerm) + _, errDeleteRole := x.Exec(`DELETE FROM role`) + require.NoError(t, errDeleteRole) + + // Test running the migrations twice to make sure they don't conflict + for i := 0; i < 2; i++ { + if i == 0 { + // put permissions + putTestPermissions(t, x, tc.putRolePerms) + } + + // Run accesscontrol migration (permissions insertion should not have conflicted) + acmigrator := migrator.NewMigrator(x, &setting.Cfg{Logger: log.New("acmigration.test")}) + acmig.AddManagedDashboardAnnotationActionsMigration(acmigrator) + + errRunningMig := acmigrator.Start(false, 0) + require.NoError(t, errRunningMig) + + // verify got == want + for orgID, roles := range tc.wantRolePerms { + for roleName := range roles { + // Check managed roles exist + role := accesscontrol.Role{} + hasRole, errRoleSearch := x.Table("role").Where("org_id = ? AND name = ?", orgID, roleName).Get(&role) + + require.NoError(t, errRoleSearch) + require.True(t, hasRole, "expected role to exist", "orgID", orgID, "role", roleName) + + // Check permissions associated with each role + perms := []accesscontrol.Permission{} + count, errManagedPermsSearch := x.Table("permission").Where("role_id = ?", role.ID).FindAndCount(&perms) + + require.NoError(t, errManagedPermsSearch) + require.Equal(t, int64(len(tc.wantRolePerms[orgID][roleName])), count, "expected role to be tied to permissions", "orgID", orgID, "role", roleName) + + gotRawPerms := convertToRawPermissions(perms) + require.ElementsMatch(t, gotRawPerms, tc.wantRolePerms[orgID][roleName], "expected role to have permissions", "orgID", orgID, "role", roleName) + + // Check assignment of the roles + br := accesscontrol.BuiltinRole{} + has, errAssignmentSearch := x.Table("builtin_role").Where("role_id = ? AND role = ? AND org_id = ?", role.ID, acmig.ParseRoleFromName(roleName), orgID).Get(&br) + require.NoError(t, errAssignmentSearch) + require.True(t, has, "expected assignment of role to builtin role", "orgID", orgID, "role", roleName) + } + } + } + }) + } +} diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/managed_permission_migrator_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/managed_permission_migrator_test.go index 94ce65f9ee7..4d8c820958e 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/managed_permission_migrator_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/managed_permission_migrator_test.go @@ -2,7 +2,6 @@ package test import ( "fmt" - "strconv" "strings" "testing" @@ -256,7 +255,10 @@ func TestManagedPermissionsMigrationRunTwice(t *testing.T) { func putTestPermissions(t *testing.T, x *xorm.Engine, rolePerms map[int64]map[string][]rawPermission) { for orgID, roles := range rolePerms { for roleName, perms := range roles { - uid := strconv.FormatInt(orgID, 10) + strings.ReplaceAll(roleName, ":", "_") + uid := strings.ReplaceAll(roleName, ":", "_") + if !strings.HasPrefix(roleName, "basic") { + uid = fmt.Sprintf("%d_%s", orgID, uid) + } role := accesscontrol.Role{ OrgID: orgID, Version: 1, diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 1fc33893d00..f7e9efa7ce2 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -114,6 +114,15 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { ualert.CreateOrgMigratedKVStoreEntries(mg) + // https://github.com/grafana/identity-access-team/issues/546: tracks removal of the feature toggle from the annotation permission migration + // nolint:staticcheck + if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil { + // nolint:staticcheck + if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAnnotationPermissionUpdate) { + accesscontrol.AddManagedDashboardAnnotationActionsMigration(mg) + } + } + addKVStoreMySQLValueTypeLongTextMigration(mg) } From b758b91e84eabd3c829bc9228180c50f56d8279f Mon Sep 17 00:00:00 2001 From: Ricky Whitaker Date: Fri, 26 Jan 2024 12:17:43 -0600 Subject: [PATCH 043/169] Update pr-codeql-analysis-go.yml to use token (#81395) Updating .github/workflows/pr-codeql-analysis-go.yml to use GH token to work in private security m irror --- .github/workflows/pr-codeql-analysis-go.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/pr-codeql-analysis-go.yml b/.github/workflows/pr-codeql-analysis-go.yml index e7fad07382d..4d9cc3760a6 100644 --- a/.github/workflows/pr-codeql-analysis-go.yml +++ b/.github/workflows/pr-codeql-analysis-go.yml @@ -16,12 +16,21 @@ jobs: runs-on: ubuntu-latest steps: + - name: "Generate token" + id: generate_token + continue-on-error: true + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a + with: + app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + - name: Checkout repository uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 + token: ${{ steps.generate_token.outputs.token }} - name: Set go version uses: actions/setup-go@v4 From 77b0369fdba1c254b64c98806eeac89f1ab6e0fd Mon Sep 17 00:00:00 2001 From: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:46:52 +0100 Subject: [PATCH 044/169] Fix `TemporaryAlert` (#81384) --- .../src/TemporaryAlert.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx index 3f7b6961e5b..84a09bdee78 100644 --- a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx +++ b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx @@ -30,22 +30,26 @@ const timeoutMap = { export const TemporaryAlert = (props: { severity: AlertVariant; text: string }) => { const style = getStyle(useTheme2()); const [visible, setVisible] = useState(false); + const [timer, setTimer] = useState(); useEffect(() => { - const timer = setTimeout(() => { - setVisible(false); - }, timeoutMap[props.severity]); - return () => { - clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } }; - }, [props.severity, visible]); + }, [timer]); useEffect(() => { if (props.text !== '') { setVisible(true); - } - }, [props.text]); - return ; + const timer = setTimeout(() => { + setVisible(false); + }, timeoutMap[props.severity]); + setTimer(timer); + } + }, [props.severity, props.text]); + + return <>{visible && }; }; From 42c9b582e0cac9569c601b85d6aa670be7393b3a Mon Sep 17 00:00:00 2001 From: Isabel <76437239+imatwawana@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:26:58 -0500 Subject: [PATCH 045/169] Docs: add saved dashboard guidance (#81406) * Added saved dashboard guidance * Ran prettier --- .../build-dashboards/annotate-visualizations/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md index ab3ec39d6d7..3481be9f6d9 100644 --- a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md @@ -44,7 +44,10 @@ Annotations are supported for the following visualization types: Grafana comes with the ability to add annotation events directly from a panel using the [built-in annotation query](#built-in-query) that exists on all dashboards. Annotations that you create this way are stored in Grafana. -To add annotations directly in the panel, the built-in query must be enabled. Learn more in [Built-in query](#built-in-query) +To add annotations directly in the panel: + +- The dashboard must already be saved. +- The built-in query must be enabled. Learn more in [Built-in query](#built-in-query). ### Add an annotation From 1213b66188f484b42f3a2a6480c7f455c6e32c1c Mon Sep 17 00:00:00 2001 From: Isabel <76437239+imatwawana@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:39:37 -0500 Subject: [PATCH 046/169] Docs: time range copy paste (#81408) Added new entry and deleted internal enablement video notes --- docs/sources/whatsnew/whats-new-in-v10-3.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/sources/whatsnew/whats-new-in-v10-3.md b/docs/sources/whatsnew/whats-new-in-v10-3.md index ccd648266a2..7c4a12a212b 100644 --- a/docs/sources/whatsnew/whats-new-in-v10-3.md +++ b/docs/sources/whatsnew/whats-new-in-v10-3.md @@ -48,7 +48,6 @@ Use full URLs for links. When linking to versioned docs, replace the version wit ## Navigation updates\* - _Available in public preview in Grafana Open Source and Enterprise_ @@ -60,8 +59,6 @@ The improved navigation menu gives you a better overview by showing all levels o - - _Available in public preview in Grafana Enterprise and Grafana Cloud_ We've improved the reporting experience with options to make all of your table data accessible in PDFs. Previously, if your dashboard included large table visualizations, you couldn't see all of the table data in your PDF report. Unlike in Grafana, you couldn't scroll in the PDF table visualization or click on the page numbers. With this new feature, you now have the option to see all the data directly in your PDF without losing your dashboard layout. @@ -80,7 +77,6 @@ To try out this feature, enable the `pdfTables` [feature toggle](https://grafana ### Moving average and trend lines using transformations - _Available in public preview in all editions of Grafana_ @@ -109,7 +105,6 @@ The transformation currently supports linear regression and polynomial regressio ### Canvas visualization supports pan and zoom - _Available in public preview in all editions of Grafana_ @@ -191,7 +186,6 @@ By default, Grafana automatically scales the unit based on the magnitude of the ### New Transformations UI experience and documentation upgrades - _Generally available in all editions of Grafana_ @@ -209,12 +203,19 @@ We've also streamlined the user experience by integrating documentation directly {{< figure src="/media/docs/grafana/transformations/transformations_internal_documentation.png" caption="Transformation documentation is now internally available inside the Grafana app itself." alt="Transformation documentation internally available" >}} +### Copy and paste time range + + + +_Generally available in all editions of Grafana_ + +Copying and pasting time range in the time range picker is now available. For example, you can copy a time range in **Explore** and paste it into **Dashboards** and vice versa. You can also copy and paste a time range using the new keyboard shortcuts `t+c` and `t+v`, respectively. + ## Profiles ### Trace to Profiles\* - _Experimental in all editions of Grafana_ @@ -379,7 +380,6 @@ The following example would add the derived field `traceID regex` based on a reg ### InfluxDB native SQL support - _Generally available in all editions of Grafana_ @@ -394,7 +394,6 @@ All you need to do is set up your InfluxDB Cloud Account and create your InfluxD ### Grafana Anonymous Access\* - _Generally available in Grafana Open Source and Enterprise_ From ad1c4b726b584e70577a15c4cf42cd69b6450d12 Mon Sep 17 00:00:00 2001 From: Summer Date: Fri, 26 Jan 2024 15:22:05 -0700 Subject: [PATCH 047/169] Grafana Build: fix release process not publishing latest storybook (#81412) Bugfix: release process not publishing latest storybook --- pkg/build/cmd/publishstorybook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/build/cmd/publishstorybook.go b/pkg/build/cmd/publishstorybook.go index b147d9a7b40..e33366d8e4b 100644 --- a/pkg/build/cmd/publishstorybook.go +++ b/pkg/build/cmd/publishstorybook.go @@ -39,7 +39,7 @@ func PublishStorybookAction(c *cli.Context) error { return err } - if latest, err := isLatest(cfg); err != nil && latest { + if latest, err := isLatest(cfg); err == nil && latest { log.Printf("Copying storybooks to latest...") if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, "latest"); err != nil { return err From 05300213960248c578afe9195a6f3e5fd9795394 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 26 Jan 2024 16:32:12 -0600 Subject: [PATCH 048/169] Field: Fix perf regression in getUniqueFieldName() (#81323) Co-authored-by: nmarrs --- packages/grafana-data/src/field/fieldState.ts | 4 +--- .../visualization/data-hover/DataHoverView.tsx | 14 +++++--------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/grafana-data/src/field/fieldState.ts b/packages/grafana-data/src/field/fieldState.ts index 04f1cc5abef..7ae0552baa2 100644 --- a/packages/grafana-data/src/field/fieldState.ts +++ b/packages/grafana-data/src/field/fieldState.ts @@ -1,5 +1,3 @@ -import { isEqual } from 'lodash'; - import { DataFrame, Field, TIME_SERIES_VALUE_FIELD_NAME, FieldType, TIME_SERIES_TIME_FIELD_NAME } from '../types'; import { formatLabels } from '../utils/labels'; @@ -167,7 +165,7 @@ export function getUniqueFieldName(field: Field, frame?: DataFrame) { for (let i = 0; i < frame.fields.length; i++) { const otherField = frame.fields[i]; - if (isEqual(field, otherField)) { + if (field === otherField) { foundSelf = true; if (dupeCount > 0) { diff --git a/public/app/features/visualization/data-hover/DataHoverView.tsx b/public/app/features/visualization/data-hover/DataHoverView.tsx index c1a346a4bee..b395aedc976 100644 --- a/public/app/features/visualization/data-hover/DataHoverView.tsx +++ b/public/app/features/visualization/data-hover/DataHoverView.tsx @@ -41,9 +41,8 @@ export function getDisplayValuesAndLinks( sortOrder?: SortOrder, mode?: TooltipDisplayMode | null ) { - const fields = data.fields.map((f, idx) => { - return { ...f, hovered: idx === columnIndex }; - }); + const fields = data.fields; + const hoveredField = columnIndex != null ? fields[columnIndex] : null; const visibleFields = fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip)); const traceIDField = visibleFields.find((field) => field.name === 'traceID') || fields[0]; @@ -63,7 +62,7 @@ export function getDisplayValuesAndLinks( const linkLookup = new Set(); for (const field of orderedVisibleFields) { - if (mode === TooltipDisplayMode.Single && columnIndex != null && !field.hovered) { + if (mode === TooltipDisplayMode.Single && field !== hoveredField) { continue; } @@ -80,14 +79,11 @@ export function getDisplayValuesAndLinks( }); } - // Sanitize field by removing hovered property to fix unique display name issue - const { hovered, ...sanitizedField } = field; - displayValues.push({ - name: getFieldDisplayName(sanitizedField, data), + name: getFieldDisplayName(field, data), value, valueString: formattedValueToString(fieldDisplay), - highlight: field.hovered, + highlight: field === hoveredField, }); } From b411aa7266129e3e7b5313317dfc7d0c8a50cdeb Mon Sep 17 00:00:00 2001 From: Andrew Hackmann <5140848+bossinc@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:53:55 -0800 Subject: [PATCH 049/169] Azure Monitor: use NewLoggerWith func instead of backend.Logger (#81124) using NewLoggerWith --- .../azuremonitor-resource-handler.go | 6 ++- .../azuremonitor-resource-handler_test.go | 3 ++ pkg/tsdb/azuremonitor/azuremonitor.go | 37 +++++++++++-------- pkg/tsdb/azuremonitor/azuremonitor_test.go | 3 +- .../azure-log-analytics-datasource.go | 10 +++-- .../metrics/azuremonitor-datasource.go | 8 ++-- .../azure-resource-graph-datasource.go | 8 ++-- pkg/tsdb/azuremonitor/types/types.go | 2 + 8 files changed, 49 insertions(+), 28 deletions(-) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go b/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go index e01e6e505d2..13d4c411b87 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -24,6 +25,7 @@ func getTarget(original string) (target string, err error) { } type httpServiceProxy struct { + logger log.Logger } func (s *httpServiceProxy) Do(rw http.ResponseWriter, req *http.Request, cli *http.Client) (http.ResponseWriter, error) { @@ -38,7 +40,7 @@ func (s *httpServiceProxy) Do(rw http.ResponseWriter, req *http.Request, cli *ht } defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + s.logger.Warn("Failed to close response body", "err", err) } }() @@ -91,7 +93,7 @@ func writeResponse(rw http.ResponseWriter, code int, msg string) { func (s *Service) handleResourceReq(subDataSource string) func(rw http.ResponseWriter, req *http.Request) { return func(rw http.ResponseWriter, req *http.Request) { - backend.Logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) + s.logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) newPath, err := getTarget(req.URL.Path) if err != nil { diff --git a/pkg/tsdb/azuremonitor/azuremonitor-resource-handler_test.go b/pkg/tsdb/azuremonitor/azuremonitor-resource-handler_test.go index d5e251f2fef..72794dd8248 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-resource-handler_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-resource-handler_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/grafana/grafana-azure-sdk-go/azsettings" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -106,6 +107,7 @@ func Test_handleResourceReq(t *testing.T) { azureMonitor: { URL: routes[azsettings.AzurePublic][azureMonitor].URL, HTTPClient: &http.Client{}, + Logger: log.DefaultLogger, }, }, }, @@ -114,6 +116,7 @@ func Test_handleResourceReq(t *testing.T) { Proxy: proxy, }, }, + logger: log.DefaultLogger, } rw := httptest.NewRecorder() req, err := http.NewRequest(http.MethodGet, "http://foo/azuremonitor/subscriptions/44693801", nil) diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 11320d52b91..50a2ee276d7 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azmoncredentials" @@ -26,19 +27,23 @@ import ( ) func ProvideService(httpClientProvider *httpclient.Provider) *Service { - proxy := &httpServiceProxy{} + logger := backend.NewLoggerWith("logger", "tsdb.azuremonitor") + proxy := &httpServiceProxy{ + logger: logger, + } executors := map[string]azDatasourceExecutor{ - azureMonitor: &metrics.AzureMonitorDatasource{Proxy: proxy}, - azureLogAnalytics: &loganalytics.AzureLogAnalyticsDatasource{Proxy: proxy}, - azureResourceGraph: &resourcegraph.AzureResourceGraphDatasource{Proxy: proxy}, - azureTraces: &loganalytics.AzureLogAnalyticsDatasource{Proxy: proxy}, + azureMonitor: &metrics.AzureMonitorDatasource{Proxy: proxy, Logger: logger}, + azureLogAnalytics: &loganalytics.AzureLogAnalyticsDatasource{Proxy: proxy, Logger: logger}, + azureResourceGraph: &resourcegraph.AzureResourceGraphDatasource{Proxy: proxy, Logger: logger}, + azureTraces: &loganalytics.AzureLogAnalyticsDatasource{Proxy: proxy, Logger: logger}, } - im := datasource.NewInstanceManager(NewInstanceSettings(httpClientProvider, executors)) + im := datasource.NewInstanceManager(NewInstanceSettings(httpClientProvider, executors, logger)) s := &Service{ im: im, executors: executors, + logger: logger, } s.queryMux = s.newQueryMux() @@ -61,9 +66,10 @@ type Service struct { queryMux *datasource.QueryTypeMux resourceHandler backend.CallResourceHandler + logger log.Logger } -func getDatasourceService(ctx context.Context, settings *backend.DataSourceInstanceSettings, azureSettings *azsettings.AzureSettings, clientProvider *httpclient.Provider, dsInfo types.DatasourceInfo, routeName string) (types.DatasourceService, error) { +func getDatasourceService(ctx context.Context, settings *backend.DataSourceInstanceSettings, azureSettings *azsettings.AzureSettings, clientProvider *httpclient.Provider, dsInfo types.DatasourceInfo, routeName string, logger log.Logger) (types.DatasourceService, error) { route := dsInfo.Routes[routeName] client, err := newHTTPClient(ctx, route, dsInfo, settings, azureSettings, clientProvider) if err != nil { @@ -72,10 +78,11 @@ func getDatasourceService(ctx context.Context, settings *backend.DataSourceInsta return types.DatasourceService{ URL: dsInfo.Routes[routeName].URL, HTTPClient: client, + Logger: logger, }, nil } -func NewInstanceSettings(clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor) datasource.InstanceFactoryFunc { +func NewInstanceSettings(clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor, logger log.Logger) datasource.InstanceFactoryFunc { return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { jsonData := map[string]any{} err := json.Unmarshal(settings.JSONData, &jsonData) @@ -91,7 +98,7 @@ func NewInstanceSettings(clientProvider *httpclient.Provider, executors map[stri azureSettings, err := azsettings.ReadSettings(ctx) if err != nil { - backend.Logger.Error("failed to read Azure settings from Grafana", "error", err.Error()) + logger.Error("failed to read Azure settings from Grafana", "error", err.Error()) return nil, err } @@ -124,7 +131,7 @@ func NewInstanceSettings(clientProvider *httpclient.Provider, executors map[stri } for routeName := range executors { - service, err := getDatasourceService(ctx, &settings, azureSettings, clientProvider, model, routeName) + service, err := getDatasourceService(ctx, &settings, azureSettings, clientProvider, model, routeName, logger) if err != nil { return nil, err } @@ -300,7 +307,7 @@ func checkAzureMonitorResourceGraphHealth(dsInfo types.DatasourceInfo, subscript return res, nil } -func metricCheckHealth(dsInfo types.DatasourceInfo) (message string, defaultSubscription string, status backend.HealthStatus) { +func metricCheckHealth(dsInfo types.DatasourceInfo, logger log.Logger) (message string, defaultSubscription string, status backend.HealthStatus) { defaultSubscription = dsInfo.Settings.SubscriptionId metricsRes, err := queryMetricHealth(dsInfo) if err != nil { @@ -323,7 +330,7 @@ func metricCheckHealth(dsInfo types.DatasourceInfo) (message string, defaultSubs } return fmt.Sprintf("Error connecting to Azure Monitor endpoint: %s", string(body)), defaultSubscription, backend.HealthStatusError } - subscriptions, err := parseSubscriptions(metricsRes) + subscriptions, err := parseSubscriptions(metricsRes, logger) if err != nil { return err.Error(), defaultSubscription, backend.HealthStatusError } @@ -387,7 +394,7 @@ func graphLogHealthCheck(dsInfo types.DatasourceInfo, defaultSubscription string return "Successfully connected to Azure Resource Graph endpoint.", backend.HealthStatusOk } -func parseSubscriptions(res *http.Response) ([]string, error) { +func parseSubscriptions(res *http.Response, logger log.Logger) ([]string, error) { var target struct { Value []struct { SubscriptionId string `json:"subscriptionId"` @@ -399,7 +406,7 @@ func parseSubscriptions(res *http.Response) ([]string, error) { } defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + logger.Warn("Failed to close response body", "err", err) } }() @@ -422,7 +429,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque status := backend.HealthStatusOk - metricsLog, defaultSubscription, metricsStatus := metricCheckHealth(dsInfo) + metricsLog, defaultSubscription, metricsStatus := metricCheckHealth(dsInfo, s.logger) if metricsStatus != backend.HealthStatusOk { status = metricsStatus } diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor_test.go index 3983682dbb3..88b8c9bfdd1 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor_test.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -87,7 +88,7 @@ func TestNewInstanceSettings(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - factory := NewInstanceSettings(&httpclient.Provider{}, map[string]azDatasourceExecutor{}) + factory := NewInstanceSettings(&httpclient.Provider{}, map[string]azDatasourceExecutor{}, log.DefaultLogger) instance, err := factory(context.Background(), tt.settings) tt.Err(t, err) if !cmp.Equal(instance, tt.expectedModel) { diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index edb636b3baf..94c76e46518 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -17,6 +17,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" "go.opentelemetry.io/otel/attribute" @@ -30,7 +31,8 @@ import ( // AzureLogAnalyticsDatasource calls the Azure Log Analytics API's type AzureLogAnalyticsDatasource struct { - Proxy types.ServiceProxy + Proxy types.ServiceProxy + Logger log.Logger } // AzureLogAnalyticsQuery is the query request that is built from the saved values for @@ -300,7 +302,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + e.Logger.Warn("Failed to close response body", "err", err) } }() @@ -610,7 +612,7 @@ func getCorrelationWorkspaces(ctx context.Context, baseResource string, resource defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + azMonService.Logger.Warn("Failed to close response body", "err", err) } }() @@ -714,7 +716,7 @@ func (e *AzureLogAnalyticsDatasource) unmarshalResponse(res *http.Response) (Azu } defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + e.Logger.Warn("Failed to close response body", "err", err) } }() diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go index 5e1eb376804..77bd4a513f5 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go @@ -14,6 +14,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" "go.opentelemetry.io/otel/attribute" @@ -27,7 +28,8 @@ import ( // AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported type AzureMonitorDatasource struct { - Proxy types.ServiceProxy + Proxy types.ServiceProxy + Logger log.Logger } var ( @@ -276,7 +278,7 @@ func (e *AzureMonitorDatasource) retrieveSubscriptionDetails(cli *http.Client, c defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + e.Logger.Warn("Failed to close response body", "err", err) } }() @@ -328,7 +330,7 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *types. defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + e.Logger.Warn("Failed to close response body", "err", err) } }() diff --git a/pkg/tsdb/azuremonitor/resourcegraph/azure-resource-graph-datasource.go b/pkg/tsdb/azuremonitor/resourcegraph/azure-resource-graph-datasource.go index c04aecc562e..518ef43ad0f 100644 --- a/pkg/tsdb/azuremonitor/resourcegraph/azure-resource-graph-datasource.go +++ b/pkg/tsdb/azuremonitor/resourcegraph/azure-resource-graph-datasource.go @@ -12,6 +12,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" "go.opentelemetry.io/otel/attribute" @@ -30,7 +31,8 @@ type AzureResourceGraphResponse struct { // AzureResourceGraphDatasource calls the Azure Resource Graph API's type AzureResourceGraphDatasource struct { - Proxy types.ServiceProxy + Proxy types.ServiceProxy + Logger log.Logger } // AzureResourceGraphQuery is the query request that is built from the saved values for @@ -160,7 +162,7 @@ func (e *AzureResourceGraphDatasource) executeQuery(ctx context.Context, query * ) defer span.End() - backend.Logger.Debug("azure resource graph query", "traceID", trace.SpanContextFromContext(ctx).TraceID()) + e.Logger.Debug("azure resource graph query", "traceID", trace.SpanContextFromContext(ctx).TraceID()) res, err := client.Do(req) if err != nil { @@ -224,7 +226,7 @@ func (e *AzureResourceGraphDatasource) unmarshalResponse(res *http.Response) (Az defer func() { if err := res.Body.Close(); err != nil { - backend.Logger.Warn("Failed to close response body", "err", err) + e.Logger.Warn("Failed to close response body", "err", err) } }() diff --git a/pkg/tsdb/azuremonitor/types/types.go b/pkg/tsdb/azuremonitor/types/types.go index 36dfa6b70ce..8b11c4e41c3 100644 --- a/pkg/tsdb/azuremonitor/types/types.go +++ b/pkg/tsdb/azuremonitor/types/types.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana-azure-sdk-go/azcredentials" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" ) @@ -44,6 +45,7 @@ type AzureMonitorCustomizedCloudSettings struct { type DatasourceService struct { URL string HTTPClient *http.Client + Logger log.Logger } type DatasourceInfo struct { From 80ac0cc9f6a9988b72f51323cdee410cb56af1a6 Mon Sep 17 00:00:00 2001 From: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Date: Sat, 27 Jan 2024 00:47:42 +0100 Subject: [PATCH 050/169] Tempo: Fix durations in TraceQL (#81418) --- public/app/plugins/datasource/tempo/package.json | 2 +- .../datasource/tempo/traceql/highlighting.test.ts | 1 + yarn.lock | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 8986f288f5f..662e3dfbd12 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -9,7 +9,7 @@ "@grafana/e2e-selectors": "workspace:*", "@grafana/experimental": "1.7.8", "@grafana/lezer-logql": "0.2.2", - "@grafana/lezer-traceql": "0.0.12", + "@grafana/lezer-traceql": "0.0.13", "@grafana/monaco-logql": "^0.0.7", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", diff --git a/public/app/plugins/datasource/tempo/traceql/highlighting.test.ts b/public/app/plugins/datasource/tempo/traceql/highlighting.test.ts index 1a3a8b90c35..e36d2b40f1b 100644 --- a/public/app/plugins/datasource/tempo/traceql/highlighting.test.ts +++ b/public/app/plugins/datasource/tempo/traceql/highlighting.test.ts @@ -168,6 +168,7 @@ describe('Highlighting', () => { ['{span.s"t\\"at"us}'], ['{span.s"t\\\\at"us}'], ['{ span.s"tat"us" = "GET123 }'], // weird query, but technically valid + ['{ duration = 123.456us}'], ])('valid query - %s', (query: string) => { expect(getErrorNodes(query)).toStrictEqual([]); }); diff --git a/yarn.lock b/yarn.lock index b9fc1883a76..87ec5537850 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3104,7 +3104,7 @@ __metadata: "@grafana/e2e-selectors": "workspace:*" "@grafana/experimental": "npm:1.7.8" "@grafana/lezer-logql": "npm:0.2.2" - "@grafana/lezer-traceql": "npm:0.0.12" + "@grafana/lezer-traceql": "npm:0.0.13" "@grafana/monaco-logql": "npm:^0.0.7" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "npm:10.4.0-pre" @@ -3479,12 +3479,12 @@ __metadata: languageName: node linkType: hard -"@grafana/lezer-traceql@npm:0.0.12": - version: 0.0.12 - resolution: "@grafana/lezer-traceql@npm:0.0.12" +"@grafana/lezer-traceql@npm:0.0.13": + version: 0.0.13 + resolution: "@grafana/lezer-traceql@npm:0.0.13" peerDependencies: "@lezer/lr": ^1.3.0 - checksum: 615c215a7c2a72b38138a322feed3de8ff85a5828bdd6933adcf54a41e078365d3bd7db2f046cb17ad21b77a59b100b24a8946369c7dc41c15cac1826dde845b + checksum: e87bebbe62e1b4b9a61576c8be3248f16d7f2739acbe339718f9797bb74a94adf9c42e34f9e8df09d3c592d591858fede2bb1867dcaffc86b9d968ea7c85d8f6 languageName: node linkType: hard From 1a1531ca5e92554d107bb1eb9df2f9fdc604d824 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Fri, 26 Jan 2024 16:49:14 -0700 Subject: [PATCH 051/169] Chore: Remove isEqual mock from field state test (#81019) --- packages/grafana-data/src/field/fieldState.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/grafana-data/src/field/fieldState.test.ts b/packages/grafana-data/src/field/fieldState.test.ts index a017962f37b..33275a58ceb 100644 --- a/packages/grafana-data/src/field/fieldState.test.ts +++ b/packages/grafana-data/src/field/fieldState.test.ts @@ -15,11 +15,6 @@ function checkScenario(scenario: TitleScenario): string { return getFieldDisplayName(field, frame, scenario.frames); } -jest.mock('lodash', () => ({ - ...jest.requireActual('lodash'), - isEqual: jest.fn().mockImplementation((obj1, obj2) => obj1 === obj2), -})); - describe('getFieldDisplayName', () => { it('Should add suffix for comparison frames', () => { const frame = toDataFrame({ From 1fab107e792f8ddaade6230947978d93523781d3 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Sun, 28 Jan 2024 15:22:45 -0800 Subject: [PATCH 052/169] FeatureFlags: Avoid using cfg.IsFeatureToggleEnabled (#81407) --- pkg/api/accesscontrol.go | 2 +- .../commands/conflict_user_command.go | 22 +- pkg/cmd/grafana/apiserver/server.go | 6 +- pkg/registry/apis/datasource/standalone.go | 4 +- .../apis/datasource/standalone_services.go | 12 +- pkg/services/featuremgmt/manager.go | 20 +- pkg/services/featuremgmt/manager_test.go | 31 -- pkg/services/featuremgmt/models.go | 1 - pkg/services/featuremgmt/registry.go | 1 - pkg/services/featuremgmt/service.go | 18 +- pkg/services/featuremgmt/service_test.go | 67 +--- pkg/services/featuremgmt/toggles_gen.csv | 316 +++++++++--------- pkg/services/featuremgmt/toggles_gen_test.go | 2 - .../pluginaccesscontrol/accesscontrol.go | 4 +- pkg/services/sqlstore/database_config.go | 10 +- pkg/services/sqlstore/database_config_test.go | 7 +- .../migrations/accesscontrol/test/ac_test.go | 7 +- .../sqlstore/migrations/migrations.go | 23 +- pkg/services/sqlstore/sqlstore.go | 74 ++-- pkg/services/store/entity/server/config.go | 2 +- pkg/setting/provider.go | 6 - 21 files changed, 248 insertions(+), 387 deletions(-) diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index cd0d92fda1b..5ccdbf7e648 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -35,7 +35,7 @@ var ( // that HTTPServer needs func (hs *HTTPServer) declareFixedRoles() error { // Declare plugins roles - if err := pluginaccesscontrol.DeclareRBACRoles(hs.accesscontrolService, hs.Cfg); err != nil { + if err := pluginaccesscontrol.DeclareRBACRoles(hs.accesscontrolService, hs.Cfg, hs.Features); err != nil { return err } diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go index 637ae88b75e..43cd08073a4 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -33,7 +33,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, error) { +func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, *featuremgmt.FeatureManager, error) { configOptions := strings.Split(cmd.String("configOverrides"), " ") configOptions = append(configOptions, cmd.Args().Slice()...) cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{ @@ -43,17 +43,19 @@ func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, error) { }) if err != nil { - return nil, err + return nil, nil, err } - return cfg, nil + + features, err := featuremgmt.ProvideManagerService(cfg) + return cfg, features, err } func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx *cli.Context) (*ConflictResolver, error) { - cfg, err := initConflictCfg(cmd) + cfg, features, err := initConflictCfg(cmd) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to load configuration", err) } - s, err := getSqlStore(cfg) + s, err := getSqlStore(cfg, features) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get to sql", err) } @@ -67,11 +69,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return nil, fmt.Errorf("%v: %w", "failed to get user service", err) } routing := routing.ProvideRegister() - featMgmt, err := featuremgmt.ProvideManagerService(cfg, nil) - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to get feature management service", err) - } - acService, err := acimpl.ProvideService(cfg, s, routing, nil, nil, nil, featMgmt) + acService, err := acimpl.ProvideService(cfg, s, routing, nil, nil, nil, features) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get access control", err) } @@ -80,13 +78,13 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return &resolver, nil } -func getSqlStore(cfg *setting.Cfg) (*sqlstore.SQLStore, error) { +func getSqlStore(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (*sqlstore.SQLStore, error) { tracer, err := tracing.ProvideService(cfg) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) } bus := bus.ProvideBus(tracer) - return sqlstore.ProvideService(cfg, &migrations.OSSMigrations{}, bus, tracer) + return sqlstore.ProvideService(cfg, features, &migrations.OSSMigrations{}, bus, tracer) } func runListConflictUsers() func(context *cli.Context) error { diff --git a/pkg/cmd/grafana/apiserver/server.go b/pkg/cmd/grafana/apiserver/server.go index bee150feab3..447567bb3de 100644 --- a/pkg/cmd/grafana/apiserver/server.go +++ b/pkg/cmd/grafana/apiserver/server.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver" "github.com/grafana/grafana/pkg/services/grafana-apiserver/utils" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) @@ -52,10 +51,7 @@ func (o *APIServerOptions) loadAPIGroupBuilders(args []string) error { case "example.grafana.app": o.builders = append(o.builders, example.NewTestingAPIBuilder()) case "featuretoggle.grafana.app": - features, err := featuremgmt.ProvideManagerService(&setting.Cfg{}, &licensing.OSSLicensingService{}) - if err != nil { - return err - } + features := featuremgmt.WithFeatureManager(setting.FeatureMgmtSettings{}, nil) // none... for now o.builders = append(o.builders, featuretoggle.NewFeatureFlagAPIBuilder(features)) case "testdata.datasource.grafana.app": ds, err := server.InitializeDataSourceAPIServer(g) diff --git a/pkg/registry/apis/datasource/standalone.go b/pkg/registry/apis/datasource/standalone.go index ea0fcff5cf0..34324bd11b8 100644 --- a/pkg/registry/apis/datasource/standalone.go +++ b/pkg/registry/apis/datasource/standalone.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request" "github.com/grafana/grafana/pkg/setting" testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource" @@ -19,6 +20,7 @@ import ( // This currently builds its dependencies manually and only works for testdata. func NewTestDataAPIServer(group string) (*DataSourceAPIBuilder, error) { pluginID := "grafana-testdata-datasource" + features := featuremgmt.WithFeatures() // None for now! if group != "testdata.datasource.grafana.app" { return nil, fmt.Errorf("only %s is currently supported", pluginID) @@ -47,7 +49,7 @@ func NewTestDataAPIServer(group string) (*DataSourceAPIBuilder, error) { return nil, err } - accessControl, pluginStore, dsService, dsCache, err := apiBuilderServices(cfg, pluginID) + accessControl, pluginStore, dsService, dsCache, err := apiBuilderServices(cfg, features, pluginID) if err != nil { return nil, err } diff --git a/pkg/registry/apis/datasource/standalone_services.go b/pkg/registry/apis/datasource/standalone_services.go index 085e25bc105..642c9a17c92 100644 --- a/pkg/registry/apis/datasource/standalone_services.go +++ b/pkg/registry/apis/datasource/standalone_services.go @@ -28,9 +28,7 @@ import ( "github.com/grafana/grafana/pkg/services/encryption/provider" encryptionService "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/kmsproviders/osskmsproviders" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/pluginsintegration/config" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -46,7 +44,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func apiBuilderServices(cfg *setting.Cfg, pluginID string) ( +func apiBuilderServices(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginID string) ( *acimpl.AccessControl, *pluginstore.Service, *datasourceService.Service, @@ -60,16 +58,14 @@ func apiBuilderServices(cfg *setting.Cfg, pluginID string) ( return nil, nil, nil, nil, err } routeRegisterImpl := routing.ProvideRegister() - hooksService := hooks.ProvideService() - ossLicensingService := licensing.ProvideService(cfg, hooksService) - featureManager, err := featuremgmt.ProvideManagerService(cfg, ossLicensingService) + featureManager, err := featuremgmt.ProvideManagerService(cfg) if err != nil { return nil, nil, nil, nil, err } inProcBus := bus.ProvideBus(tracingService) - ossMigrations := migrations.ProvideOSSMigrations() - sqlStore, err := sqlstore.ProvideService(cfg, ossMigrations, inProcBus, tracingService) + ossMigrations := migrations.ProvideOSSMigrations(features) + sqlStore, err := sqlstore.ProvideService(cfg, features, ossMigrations, inProcBus, tracingService) if err != nil { return nil, nil, nil, nil, err } diff --git a/pkg/services/featuremgmt/manager.go b/pkg/services/featuremgmt/manager.go index 0dabd4d6f76..7aba0951001 100644 --- a/pkg/services/featuremgmt/manager.go +++ b/pkg/services/featuremgmt/manager.go @@ -6,7 +6,6 @@ import ( "reflect" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) @@ -20,12 +19,11 @@ type FeatureManager struct { Settings setting.FeatureMgmtSettings - licensing licensing.Licensing - flags map[string]*FeatureFlag - enabled map[string]bool // only the "on" values - startup map[string]bool // the explicit values registered at startup - warnings map[string]string // potential warnings about the flag - log log.Logger + flags map[string]*FeatureFlag + enabled map[string]bool // only the "on" values + startup map[string]bool // the explicit values registered at startup + warnings map[string]string // potential warnings about the flag + log log.Logger } // This will merge the flags with the current configuration @@ -62,10 +60,6 @@ func (fm *FeatureManager) registerFlags(flags ...FeatureFlag) { flag.RequiresDevMode = true } - if add.RequiresLicense { - flag.RequiresLicense = true - } - if add.RequiresRestart { flag.RequiresRestart = true } @@ -81,10 +75,6 @@ func (fm *FeatureManager) meetsRequirements(ff *FeatureFlag) (bool, string) { return false, "requires dev mode" } - if ff.RequiresLicense && (fm.licensing == nil || !fm.licensing.FeatureEnabled(ff.Name)) { - return false, "license requirement" - } - return true, "" } diff --git a/pkg/services/featuremgmt/manager_test.go b/pkg/services/featuremgmt/manager_test.go index c65e8585c16..434012df7bd 100644 --- a/pkg/services/featuremgmt/manager_test.go +++ b/pkg/services/featuremgmt/manager_test.go @@ -24,37 +24,6 @@ func TestFeatureManager(t *testing.T) { require.Equal(t, map[string]bool{"a": true}, ft.GetEnabled(context.Background())) }) - t.Run("check license validation", func(t *testing.T) { - ft := FeatureManager{ - flags: map[string]*FeatureFlag{}, - warnings: map[string]string{}, - } - ft.registerFlags(FeatureFlag{ - Name: "a", - RequiresLicense: true, - RequiresDevMode: true, - Expression: "true", - }, FeatureFlag{ - Name: "b", - Expression: "true", - }) - require.False(t, ft.IsEnabledGlobally("a")) - require.True(t, ft.IsEnabledGlobally("b")) - require.False(t, ft.IsEnabledGlobally("c")) // uknown flag - - // Try changing "requires license" - ft.registerFlags(FeatureFlag{ - Name: "a", - RequiresLicense: false, // shuld still require license! - }, FeatureFlag{ - Name: "b", - RequiresLicense: true, // expression is still "true" - }) - require.False(t, ft.IsEnabledGlobally("a")) - require.False(t, ft.IsEnabledGlobally("b")) - require.False(t, ft.IsEnabledGlobally("c")) - }) - t.Run("check description and docs configs", func(t *testing.T) { ft := FeatureManager{ flags: map[string]*FeatureFlag{}, diff --git a/pkg/services/featuremgmt/models.go b/pkg/services/featuremgmt/models.go index f941933002b..eb59416b074 100644 --- a/pkg/services/featuremgmt/models.go +++ b/pkg/services/featuremgmt/models.go @@ -127,7 +127,6 @@ type FeatureFlag struct { // Special behavior properties RequiresDevMode bool `json:"requiresDevMode,omitempty"` // can not be enabled in production - RequiresLicense bool `json:"requiresLicense,omitempty"` // Must be enabled in the license FrontendOnly bool `json:"frontend,omitempty"` // change is only seen in the frontend HideFromDocs bool `json:"hideFromDocs,omitempty"` // don't add the values to docs diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 896b7d53a0d..1dd8c7f2a5a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -59,7 +59,6 @@ var ( Name: "publicDashboardsEmailSharing", Description: "Enables public dashboard sharing to be restricted to only allowed emails", Stage: FeatureStagePublicPreview, - RequiresLicense: true, Owner: grafanaSharingSquad, HideFromDocs: true, HideFromAdminPage: true, diff --git a/pkg/services/featuremgmt/service.go b/pkg/services/featuremgmt/service.go index 0c25dd77f00..137b8e174b4 100644 --- a/pkg/services/featuremgmt/service.go +++ b/pkg/services/featuremgmt/service.go @@ -5,7 +5,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) @@ -18,16 +17,15 @@ var ( }, []string{"name"}) ) -func ProvideManagerService(cfg *setting.Cfg, licensing licensing.Licensing) (*FeatureManager, error) { +func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) { mgmt := &FeatureManager{ - isDevMod: cfg.Env != setting.Prod, - licensing: licensing, - flags: make(map[string]*FeatureFlag, 30), - enabled: make(map[string]bool), - startup: make(map[string]bool), - warnings: make(map[string]string), - Settings: cfg.FeatureManagement, - log: log.New("featuremgmt"), + isDevMod: cfg.Env != setting.Prod, + flags: make(map[string]*FeatureFlag, 30), + enabled: make(map[string]bool), + startup: make(map[string]bool), + warnings: make(map[string]string), + Settings: cfg.FeatureManagement, + log: log.New("featuremgmt"), } // Register the standard flags diff --git a/pkg/services/featuremgmt/service_test.go b/pkg/services/featuremgmt/service_test.go index b69753fa362..49d85e98e06 100644 --- a/pkg/services/featuremgmt/service_test.go +++ b/pkg/services/featuremgmt/service_test.go @@ -5,40 +5,12 @@ import ( "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) func TestFeatureService(t *testing.T) { - license := stubLicenseServier{ - flags: []FeatureFlag{ - { - Name: "a.yes.default", - RequiresLicense: true, - Expression: "true", - }, - { - Name: "a.yes", - RequiresLicense: true, - Expression: "", - }, - { - Name: "b.no", - RequiresLicense: true, - }, - }, - enabled: map[string]bool{ - "a.yes.default": true, - "a.yes": true, - }, - } - require.False(t, license.FeatureEnabled("unknown")) - require.False(t, license.FeatureEnabled("b.no")) - require.True(t, license.FeatureEnabled("a.yes")) - require.True(t, license.FeatureEnabled("a.yes.default")) - cfg := setting.NewCfg() - mgmt, err := ProvideManagerService(cfg, license) + mgmt, err := ProvideManagerService(cfg) require.NoError(t, err) require.NotNil(t, mgmt) @@ -46,40 +18,3 @@ func TestFeatureService(t *testing.T) { require.False(t, mgmt.IsEnabledGlobally("a.yes.default")) require.False(t, mgmt.IsEnabledGlobally("a.yes")) // licensed, but not enabled } - -var ( - _ licensing.Licensing = (*stubLicenseServier)(nil) -) - -type stubLicenseServier struct { - flags []FeatureFlag - enabled map[string]bool -} - -func (s stubLicenseServier) Expiry() int64 { - return 100 -} - -func (s stubLicenseServier) Edition() string { - return "test" -} - -func (s stubLicenseServier) ContentDeliveryPrefix() string { - return "" -} - -func (s stubLicenseServier) LicenseURL(showAdminLicensingPage bool) string { - return "http://??" -} - -func (s stubLicenseServier) StateInfo() string { - return "ok" -} - -func (s stubLicenseServier) EnabledFeatures() map[string]bool { - return map[string]bool{} -} - -func (s stubLicenseServier) FeatureEnabled(feature string) bool { - return s.enabled[feature] -} diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 2b543058c1f..6da65ba3643 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -1,158 +1,158 @@ -Name,Stage,Owner,Created,requiresDevMode,RequiresLicense,RequiresRestart,FrontendOnly -disableEnvelopeEncryption,GA,@grafana/grafana-as-code,2022-05-24,false,false,false,false -live-service-web-worker,experimental,@grafana/grafana-app-platform-squad,2021-11-09,false,false,false,true -queryOverLive,experimental,@grafana/grafana-app-platform-squad,2022-01-05,false,false,false,true -panelTitleSearch,preview,@grafana/grafana-app-platform-squad,2022-02-15,false,false,false,false -publicDashboards,GA,@grafana/sharing-squad,2022-04-07,false,false,false,false -publicDashboardsEmailSharing,preview,@grafana/sharing-squad,2022-12-21,false,true,false,false -lokiExperimentalStreaming,experimental,@grafana/observability-logs,2023-06-19,false,false,false,false -featureHighlights,GA,@grafana/grafana-as-code,2022-02-03,false,false,false,false -migrationLocking,preview,@grafana/backend-platform,2022-02-15,false,false,false,false -storage,experimental,@grafana/grafana-app-platform-squad,2022-03-17,false,false,false,false -correlations,GA,@grafana/explore-squad,2022-09-16,false,false,false,false -exploreContentOutline,GA,@grafana/explore-squad,2023-11-03,false,false,false,true -datasourceQueryMultiStatus,experimental,@grafana/plugins-platform-backend,2022-05-03,false,false,false,false -traceToMetrics,experimental,@grafana/observability-traces-and-profiling,2022-03-07,false,false,false,true -autoMigrateOldPanels,preview,@grafana/dataviz-squad,2022-06-11,false,false,false,true -disableAngular,preview,@grafana/dataviz-squad,2023-03-23,false,false,false,true -canvasPanelNesting,experimental,@grafana/dataviz-squad,2022-05-31,false,false,false,true -newVizTooltips,preview,@grafana/dataviz-squad,2023-11-03,false,false,false,true -scenes,experimental,@grafana/dashboards-squad,2022-07-07,false,false,false,true -disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,2022-07-13,false,false,true,false -logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,2022-06-10,false,false,false,false -dataConnectionsConsole,GA,@grafana/plugins-platform-backend,2022-06-01,false,false,false,false -topnav,deprecated,@grafana/grafana-frontend-platform,2022-06-20,false,false,false,false -dockedMegaMenu,experimental,@grafana/grafana-frontend-platform,2023-09-18,false,false,false,true -returnToPrevious,experimental,@grafana/grafana-frontend-platform,2024-01-09,false,false,false,true -grpcServer,preview,@grafana/grafana-app-platform-squad,2022-09-27,false,false,false,false -unifiedStorage,experimental,@grafana/grafana-app-platform-squad,2022-12-01,true,false,true,false -cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,2022-11-28,false,false,false,false -redshiftAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,false,false -athenaAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,false,true -cloudwatchNewRegionsHandler,GA,@grafana/aws-datasources,2023-09-25,false,false,false,false -showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,2022-10-14,false,false,false,false -mysqlAnsiQuotes,experimental,@grafana/backend-platform,2022-10-12,false,false,false,false -accessControlOnCall,preview,@grafana/identity-access-team,2022-10-19,false,false,false,false -nestedFolders,preview,@grafana/backend-platform,2022-10-22,false,false,false,false -nestedFolderPicker,GA,@grafana/grafana-frontend-platform,2023-07-24,false,false,false,true -disablePrometheusExemplarSampling,GA,@grafana/observability-metrics,2022-12-19,false,false,false,false -alertingBacktesting,experimental,@grafana/alerting-squad,2022-10-20,false,false,false,false -editPanelCSVDragAndDrop,experimental,@grafana/grafana-bi-squad,2022-12-20,false,false,false,true -alertingNoNormalState,preview,@grafana/alerting-squad,2023-01-14,false,false,false,false -logsContextDatasourceUi,GA,@grafana/observability-logs,2023-01-27,false,false,false,true -lokiQuerySplitting,GA,@grafana/observability-logs,2023-02-09,false,false,false,true -lokiQuerySplittingConfig,experimental,@grafana/observability-logs,2023-03-20,false,false,false,true -individualCookiePreferences,experimental,@grafana/backend-platform,2023-02-23,false,false,false,false -prometheusMetricEncyclopedia,GA,@grafana/observability-metrics,2023-03-07,false,false,false,true -influxdbBackendMigration,GA,@grafana/observability-metrics,2023-03-15,false,false,false,true -influxqlStreamingParser,experimental,@grafana/observability-metrics,2023-11-29,false,false,false,false -clientTokenRotation,GA,@grafana/identity-access-team,2023-03-23,false,false,false,false -prometheusDataplane,GA,@grafana/observability-metrics,2023-03-29,false,false,false,false -lokiMetricDataplane,GA,@grafana/observability-logs,2023-04-13,false,false,false,false -lokiLogsDataplane,experimental,@grafana/observability-logs,2023-07-13,false,false,false,false -dataplaneFrontendFallback,GA,@grafana/observability-metrics,2023-04-24,false,false,false,true -disableSSEDataplane,experimental,@grafana/observability-metrics,2023-04-24,false,false,false,false -alertStateHistoryLokiSecondary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false,false -alertStateHistoryLokiPrimary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false,false -alertStateHistoryLokiOnly,experimental,@grafana/alerting-squad,2023-03-30,false,false,false,false -unifiedRequestLog,experimental,@grafana/backend-platform,2023-03-31,false,false,false,false -renderAuthJWT,preview,@grafana/grafana-as-code,2023-04-03,false,false,false,false -externalServiceAuth,experimental,@grafana/identity-access-team,2023-04-11,true,false,false,false -refactorVariablesTimeRange,preview,@grafana/dashboards-squad,2023-06-06,false,false,false,false -enableElasticsearchBackendQuerying,GA,@grafana/observability-logs,2023-04-14,false,false,false,false -advancedDataSourcePicker,GA,@grafana/dashboards-squad,2023-04-14,false,false,false,true -faroDatasourceSelector,preview,@grafana/app-o11y,2023-05-04,false,false,false,true -enableDatagridEditing,preview,@grafana/grafana-bi-squad,2023-04-24,false,false,false,true -extraThemes,experimental,@grafana/grafana-frontend-platform,2023-05-10,false,false,false,true -lokiPredefinedOperations,experimental,@grafana/observability-logs,2023-06-02,false,false,false,true -pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,2023-06-05,false,false,false,true -dashboardEmbed,experimental,@grafana/grafana-as-code,2023-07-06,false,false,false,true -frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,2023-07-05,false,false,false,true -sqlDatasourceDatabaseSelection,preview,@grafana/grafana-bi-squad,2023-06-06,false,false,false,true -lokiFormatQuery,experimental,@grafana/observability-logs,2023-06-21,false,false,false,true -cloudWatchLogsMonacoEditor,GA,@grafana/aws-datasources,2023-06-12,false,false,false,true -exploreScrollableLogsContainer,experimental,@grafana/observability-logs,2023-06-15,false,false,false,true -recordedQueriesMulti,GA,@grafana/observability-metrics,2023-06-14,false,false,false,false -pluginsDynamicAngularDetectionPatterns,experimental,@grafana/plugins-platform-backend,2023-06-26,false,false,false,false -vizAndWidgetSplit,experimental,@grafana/dashboards-squad,2023-06-27,false,false,false,true -prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,2023-07-05,false,false,false,true -logsExploreTableVisualisation,experimental,@grafana/observability-logs,2023-07-12,false,false,false,true -awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,2023-07-06,false,false,false,false -transformationsRedesign,GA,@grafana/observability-metrics,2023-07-12,false,false,false,true -mlExpressions,experimental,@grafana/alerting-squad,2023-07-13,false,false,false,false -traceQLStreaming,experimental,@grafana/observability-traces-and-profiling,2023-07-26,false,false,false,true -metricsSummary,experimental,@grafana/observability-traces-and-profiling,2023-08-28,false,false,false,true -grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,2023-10-06,true,false,true,false -grafanaAPIServerEnsureKubectlAccess,experimental,@grafana/grafana-app-platform-squad,2023-12-06,true,false,true,false -featureToggleAdminPage,experimental,@grafana/grafana-operator-experience-squad,2023-07-18,false,false,true,false -awsAsyncQueryCaching,GA,@grafana/aws-datasources,2023-07-21,false,false,false,false -splitScopes,GA,@grafana/identity-access-team,2023-07-21,false,false,true,false -permissionsFilterRemoveSubquery,experimental,@grafana/backend-platform,2023-08-02,false,false,false,false -prometheusConfigOverhaulAuth,GA,@grafana/observability-metrics,2023-07-21,false,false,false,false -configurableSchedulerTick,experimental,@grafana/alerting-squad,2023-07-26,false,false,true,false -influxdbSqlSupport,GA,@grafana/observability-metrics,2023-08-02,false,false,true,false -alertingNoDataErrorExecution,GA,@grafana/alerting-squad,2023-08-15,false,false,true,false -angularDeprecationUI,experimental,@grafana/plugins-platform-backend,2023-08-29,false,false,false,true -dashgpt,preview,@grafana/dashboards-squad,2023-11-17,false,false,false,true -reportingRetries,preview,@grafana/sharing-squad,2023-08-31,false,false,true,false -sseGroupByDatasource,experimental,@grafana/observability-metrics,2023-09-07,false,false,false,false -requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-09-11,false,false,false,false -libraryPanelRBAC,experimental,@grafana/dashboards-squad,2023-10-11,false,false,true,false -lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,2023-09-19,false,false,false,false -wargamesTesting,experimental,@grafana/hosted-grafana-team,2023-09-13,false,false,false,false -alertingInsights,GA,@grafana/alerting-squad,2023-09-14,false,false,false,true -externalCorePlugins,experimental,@grafana/plugins-platform-backend,2023-09-22,false,false,false,false -pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,2023-09-21,false,false,false,true -httpSLOLevels,experimental,@grafana/hosted-grafana-team,2023-09-22,false,false,true,false -idForwarding,experimental,@grafana/identity-access-team,2023-09-25,false,false,false,false -cloudWatchWildCardDimensionValues,GA,@grafana/aws-datasources,2023-09-27,false,false,false,false -externalServiceAccounts,privatePreview,@grafana/identity-access-team,2023-09-28,false,false,false,false -panelMonitoring,experimental,@grafana/dataviz-squad,2023-10-08,false,false,false,true -enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,2023-10-03,false,false,false,false -formatString,preview,@grafana/grafana-bi-squad,2023-10-13,false,false,false,true -transformationsVariableSupport,preview,@grafana/grafana-bi-squad,2023-10-04,false,false,false,true -kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,2023-11-08,false,false,true,false -kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,2023-12-04,false,false,true,false -cloudWatchBatchQueries,preview,@grafana/aws-datasources,2023-10-20,false,false,false,false -recoveryThreshold,GA,@grafana/alerting-squad,2023-10-10,false,false,true,false -lokiStructuredMetadata,experimental,@grafana/observability-logs,2023-11-16,false,false,false,false -teamHttpHeaders,experimental,@grafana/identity-access-team,2023-10-17,false,false,false,false -awsDatasourcesNewFormStyling,experimental,@grafana/aws-datasources,2023-10-12,false,false,false,true -cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,2023-10-12,false,false,false,false -panelTitleSearchInV1,experimental,@grafana/backend-platform,2023-10-13,true,false,false,false -pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-10-17,false,false,false,false -managedPluginsInstall,preview,@grafana/plugins-platform-backend,2023-10-18,false,false,false,false -prometheusPromQAIL,experimental,@grafana/observability-metrics,2023-10-19,false,false,false,true -addFieldFromCalculationStatFunctions,preview,@grafana/grafana-bi-squad,2023-11-03,false,false,false,true -alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false,false -alertmanagerRemotePrimary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false,false -alertmanagerRemoteOnly,experimental,@grafana/alerting-squad,2023-10-30,false,false,false,false -annotationPermissionUpdate,experimental,@grafana/identity-access-team,2023-10-31,false,false,false,false -extractFieldsNameDeduplication,experimental,@grafana/grafana-bi-squad,2023-11-02,false,false,false,true -dashboardSceneForViewers,experimental,@grafana/dashboards-squad,2023-11-02,false,false,false,true -dashboardScene,experimental,@grafana/dashboards-squad,2023-11-13,false,false,false,true -panelFilterVariable,experimental,@grafana/dashboards-squad,2023-11-03,false,false,false,true -pdfTables,preview,@grafana/sharing-squad,2023-11-06,false,false,false,false -ssoSettingsApi,experimental,@grafana/identity-access-team,2023-11-08,false,false,false,false -canvasPanelPanZoom,preview,@grafana/dataviz-squad,2023-12-27,false,false,false,true -logsInfiniteScrolling,experimental,@grafana/observability-logs,2023-11-09,false,false,false,true -flameGraphItemCollapsing,experimental,@grafana/observability-traces-and-profiling,2023-11-09,false,false,false,true -alertingDetailsViewV2,experimental,@grafana/alerting-squad,2023-11-09,false,false,false,true -datatrails,experimental,@grafana/dashboards-squad,2023-11-15,false,false,false,true -alertingSimplifiedRouting,experimental,@grafana/alerting-squad,2023-11-10,false,false,false,false -logRowsPopoverMenu,GA,@grafana/observability-logs,2023-11-16,false,false,false,true -pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,2023-11-15,false,false,false,false -tableSharedCrosshair,experimental,@grafana/grafana-bi-squad,2023-12-12,false,false,false,true -regressionTransformation,preview,@grafana/grafana-bi-squad,2023-11-24,false,false,false,true -displayAnonymousStats,GA,@grafana/identity-access-team,2023-11-29,false,false,false,true -lokiQueryHints,GA,@grafana/observability-logs,2023-12-18,false,false,false,true -kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,2023-12-22,false,false,false,true -alertingPreviewUpgrade,preview,@grafana/alerting-squad,2024-01-03,false,false,true,false -enablePluginsTracingByDefault,experimental,@grafana/plugins-platform-backend,2024-01-09,false,false,true,false -cloudRBACRoles,experimental,@grafana/identity-access-team,2024-01-10,false,false,true,false -alertingQueryOptimization,GA,@grafana/alerting-squad,2024-01-10,false,false,false,false -newFolderPicker,experimental,@grafana/grafana-frontend-platform,2024-01-12,false,false,false,true -jitterAlertRules,GA,@grafana/alerting-squad,2024-01-17,false,false,true,false -jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,2024-01-17,false,false,true,false -onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,2024-01-22,false,false,false,false -alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,2024-01-22,false,false,false,false +Name,Stage,Owner,Created,requiresDevMode,RequiresRestart,FrontendOnly +disableEnvelopeEncryption,GA,@grafana/grafana-as-code,2022-05-24,false,false,false +live-service-web-worker,experimental,@grafana/grafana-app-platform-squad,2021-11-09,false,false,true +queryOverLive,experimental,@grafana/grafana-app-platform-squad,2022-01-05,false,false,true +panelTitleSearch,preview,@grafana/grafana-app-platform-squad,2022-02-15,false,false,false +publicDashboards,GA,@grafana/sharing-squad,2022-04-07,false,false,false +publicDashboardsEmailSharing,preview,@grafana/sharing-squad,2022-12-21,false,false,false +lokiExperimentalStreaming,experimental,@grafana/observability-logs,2023-06-19,false,false,false +featureHighlights,GA,@grafana/grafana-as-code,2022-02-03,false,false,false +migrationLocking,preview,@grafana/backend-platform,2022-02-15,false,false,false +storage,experimental,@grafana/grafana-app-platform-squad,2022-03-17,false,false,false +correlations,GA,@grafana/explore-squad,2022-09-16,false,false,false +exploreContentOutline,GA,@grafana/explore-squad,2023-11-03,false,false,true +datasourceQueryMultiStatus,experimental,@grafana/plugins-platform-backend,2022-05-03,false,false,false +traceToMetrics,experimental,@grafana/observability-traces-and-profiling,2022-03-07,false,false,true +autoMigrateOldPanels,preview,@grafana/dataviz-squad,2022-06-11,false,false,true +disableAngular,preview,@grafana/dataviz-squad,2023-03-23,false,false,true +canvasPanelNesting,experimental,@grafana/dataviz-squad,2022-05-31,false,false,true +newVizTooltips,preview,@grafana/dataviz-squad,2023-11-03,false,false,true +scenes,experimental,@grafana/dashboards-squad,2022-07-07,false,false,true +disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,2022-07-13,false,true,false +logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,2022-06-10,false,false,false +dataConnectionsConsole,GA,@grafana/plugins-platform-backend,2022-06-01,false,false,false +topnav,deprecated,@grafana/grafana-frontend-platform,2022-06-20,false,false,false +dockedMegaMenu,experimental,@grafana/grafana-frontend-platform,2023-09-18,false,false,true +returnToPrevious,experimental,@grafana/grafana-frontend-platform,2024-01-09,false,false,true +grpcServer,preview,@grafana/grafana-app-platform-squad,2022-09-27,false,false,false +unifiedStorage,experimental,@grafana/grafana-app-platform-squad,2022-12-01,true,true,false +cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,2022-11-28,false,false,false +redshiftAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,false +athenaAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,true +cloudwatchNewRegionsHandler,GA,@grafana/aws-datasources,2023-09-25,false,false,false +showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,2022-10-14,false,false,false +mysqlAnsiQuotes,experimental,@grafana/backend-platform,2022-10-12,false,false,false +accessControlOnCall,preview,@grafana/identity-access-team,2022-10-19,false,false,false +nestedFolders,preview,@grafana/backend-platform,2022-10-22,false,false,false +nestedFolderPicker,GA,@grafana/grafana-frontend-platform,2023-07-24,false,false,true +disablePrometheusExemplarSampling,GA,@grafana/observability-metrics,2022-12-19,false,false,false +alertingBacktesting,experimental,@grafana/alerting-squad,2022-10-20,false,false,false +editPanelCSVDragAndDrop,experimental,@grafana/grafana-bi-squad,2022-12-20,false,false,true +alertingNoNormalState,preview,@grafana/alerting-squad,2023-01-14,false,false,false +logsContextDatasourceUi,GA,@grafana/observability-logs,2023-01-27,false,false,true +lokiQuerySplitting,GA,@grafana/observability-logs,2023-02-09,false,false,true +lokiQuerySplittingConfig,experimental,@grafana/observability-logs,2023-03-20,false,false,true +individualCookiePreferences,experimental,@grafana/backend-platform,2023-02-23,false,false,false +prometheusMetricEncyclopedia,GA,@grafana/observability-metrics,2023-03-07,false,false,true +influxdbBackendMigration,GA,@grafana/observability-metrics,2023-03-15,false,false,true +influxqlStreamingParser,experimental,@grafana/observability-metrics,2023-11-29,false,false,false +clientTokenRotation,GA,@grafana/identity-access-team,2023-03-23,false,false,false +prometheusDataplane,GA,@grafana/observability-metrics,2023-03-29,false,false,false +lokiMetricDataplane,GA,@grafana/observability-logs,2023-04-13,false,false,false +lokiLogsDataplane,experimental,@grafana/observability-logs,2023-07-13,false,false,false +dataplaneFrontendFallback,GA,@grafana/observability-metrics,2023-04-24,false,false,true +disableSSEDataplane,experimental,@grafana/observability-metrics,2023-04-24,false,false,false +alertStateHistoryLokiSecondary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false +alertStateHistoryLokiPrimary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false +alertStateHistoryLokiOnly,experimental,@grafana/alerting-squad,2023-03-30,false,false,false +unifiedRequestLog,experimental,@grafana/backend-platform,2023-03-31,false,false,false +renderAuthJWT,preview,@grafana/grafana-as-code,2023-04-03,false,false,false +externalServiceAuth,experimental,@grafana/identity-access-team,2023-04-11,true,false,false +refactorVariablesTimeRange,preview,@grafana/dashboards-squad,2023-06-06,false,false,false +enableElasticsearchBackendQuerying,GA,@grafana/observability-logs,2023-04-14,false,false,false +advancedDataSourcePicker,GA,@grafana/dashboards-squad,2023-04-14,false,false,true +faroDatasourceSelector,preview,@grafana/app-o11y,2023-05-04,false,false,true +enableDatagridEditing,preview,@grafana/grafana-bi-squad,2023-04-24,false,false,true +extraThemes,experimental,@grafana/grafana-frontend-platform,2023-05-10,false,false,true +lokiPredefinedOperations,experimental,@grafana/observability-logs,2023-06-02,false,false,true +pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,2023-06-05,false,false,true +dashboardEmbed,experimental,@grafana/grafana-as-code,2023-07-06,false,false,true +frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,2023-07-05,false,false,true +sqlDatasourceDatabaseSelection,preview,@grafana/grafana-bi-squad,2023-06-06,false,false,true +lokiFormatQuery,experimental,@grafana/observability-logs,2023-06-21,false,false,true +cloudWatchLogsMonacoEditor,GA,@grafana/aws-datasources,2023-06-12,false,false,true +exploreScrollableLogsContainer,experimental,@grafana/observability-logs,2023-06-15,false,false,true +recordedQueriesMulti,GA,@grafana/observability-metrics,2023-06-14,false,false,false +pluginsDynamicAngularDetectionPatterns,experimental,@grafana/plugins-platform-backend,2023-06-26,false,false,false +vizAndWidgetSplit,experimental,@grafana/dashboards-squad,2023-06-27,false,false,true +prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,2023-07-05,false,false,true +logsExploreTableVisualisation,experimental,@grafana/observability-logs,2023-07-12,false,false,true +awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,2023-07-06,false,false,false +transformationsRedesign,GA,@grafana/observability-metrics,2023-07-12,false,false,true +mlExpressions,experimental,@grafana/alerting-squad,2023-07-13,false,false,false +traceQLStreaming,experimental,@grafana/observability-traces-and-profiling,2023-07-26,false,false,true +metricsSummary,experimental,@grafana/observability-traces-and-profiling,2023-08-28,false,false,true +grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,2023-10-06,true,true,false +grafanaAPIServerEnsureKubectlAccess,experimental,@grafana/grafana-app-platform-squad,2023-12-06,true,true,false +featureToggleAdminPage,experimental,@grafana/grafana-operator-experience-squad,2023-07-18,false,true,false +awsAsyncQueryCaching,GA,@grafana/aws-datasources,2023-07-21,false,false,false +splitScopes,GA,@grafana/identity-access-team,2023-07-21,false,true,false +permissionsFilterRemoveSubquery,experimental,@grafana/backend-platform,2023-08-02,false,false,false +prometheusConfigOverhaulAuth,GA,@grafana/observability-metrics,2023-07-21,false,false,false +configurableSchedulerTick,experimental,@grafana/alerting-squad,2023-07-26,false,true,false +influxdbSqlSupport,GA,@grafana/observability-metrics,2023-08-02,false,true,false +alertingNoDataErrorExecution,GA,@grafana/alerting-squad,2023-08-15,false,true,false +angularDeprecationUI,experimental,@grafana/plugins-platform-backend,2023-08-29,false,false,true +dashgpt,preview,@grafana/dashboards-squad,2023-11-17,false,false,true +reportingRetries,preview,@grafana/sharing-squad,2023-08-31,false,true,false +sseGroupByDatasource,experimental,@grafana/observability-metrics,2023-09-07,false,false,false +requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-09-11,false,false,false +libraryPanelRBAC,experimental,@grafana/dashboards-squad,2023-10-11,false,true,false +lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,2023-09-19,false,false,false +wargamesTesting,experimental,@grafana/hosted-grafana-team,2023-09-13,false,false,false +alertingInsights,GA,@grafana/alerting-squad,2023-09-14,false,false,true +externalCorePlugins,experimental,@grafana/plugins-platform-backend,2023-09-22,false,false,false +pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,2023-09-21,false,false,true +httpSLOLevels,experimental,@grafana/hosted-grafana-team,2023-09-22,false,true,false +idForwarding,experimental,@grafana/identity-access-team,2023-09-25,false,false,false +cloudWatchWildCardDimensionValues,GA,@grafana/aws-datasources,2023-09-27,false,false,false +externalServiceAccounts,privatePreview,@grafana/identity-access-team,2023-09-28,false,false,false +panelMonitoring,experimental,@grafana/dataviz-squad,2023-10-08,false,false,true +enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,2023-10-03,false,false,false +formatString,preview,@grafana/grafana-bi-squad,2023-10-13,false,false,true +transformationsVariableSupport,preview,@grafana/grafana-bi-squad,2023-10-04,false,false,true +kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,2023-11-08,false,true,false +kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,2023-12-04,false,true,false +cloudWatchBatchQueries,preview,@grafana/aws-datasources,2023-10-20,false,false,false +recoveryThreshold,GA,@grafana/alerting-squad,2023-10-10,false,true,false +lokiStructuredMetadata,experimental,@grafana/observability-logs,2023-11-16,false,false,false +teamHttpHeaders,experimental,@grafana/identity-access-team,2023-10-17,false,false,false +awsDatasourcesNewFormStyling,experimental,@grafana/aws-datasources,2023-10-12,false,false,true +cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,2023-10-12,false,false,false +panelTitleSearchInV1,experimental,@grafana/backend-platform,2023-10-13,true,false,false +pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-10-17,false,false,false +managedPluginsInstall,preview,@grafana/plugins-platform-backend,2023-10-18,false,false,false +prometheusPromQAIL,experimental,@grafana/observability-metrics,2023-10-19,false,false,true +addFieldFromCalculationStatFunctions,preview,@grafana/grafana-bi-squad,2023-11-03,false,false,true +alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false +alertmanagerRemotePrimary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false +alertmanagerRemoteOnly,experimental,@grafana/alerting-squad,2023-10-30,false,false,false +annotationPermissionUpdate,experimental,@grafana/identity-access-team,2023-10-31,false,false,false +extractFieldsNameDeduplication,experimental,@grafana/grafana-bi-squad,2023-11-02,false,false,true +dashboardSceneForViewers,experimental,@grafana/dashboards-squad,2023-11-02,false,false,true +dashboardScene,experimental,@grafana/dashboards-squad,2023-11-13,false,false,true +panelFilterVariable,experimental,@grafana/dashboards-squad,2023-11-03,false,false,true +pdfTables,preview,@grafana/sharing-squad,2023-11-06,false,false,false +ssoSettingsApi,experimental,@grafana/identity-access-team,2023-11-08,false,false,false +canvasPanelPanZoom,preview,@grafana/dataviz-squad,2023-12-27,false,false,true +logsInfiniteScrolling,experimental,@grafana/observability-logs,2023-11-09,false,false,true +flameGraphItemCollapsing,experimental,@grafana/observability-traces-and-profiling,2023-11-09,false,false,true +alertingDetailsViewV2,experimental,@grafana/alerting-squad,2023-11-09,false,false,true +datatrails,experimental,@grafana/dashboards-squad,2023-11-15,false,false,true +alertingSimplifiedRouting,experimental,@grafana/alerting-squad,2023-11-10,false,false,false +logRowsPopoverMenu,GA,@grafana/observability-logs,2023-11-16,false,false,true +pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,2023-11-15,false,false,false +tableSharedCrosshair,experimental,@grafana/grafana-bi-squad,2023-12-12,false,false,true +regressionTransformation,preview,@grafana/grafana-bi-squad,2023-11-24,false,false,true +displayAnonymousStats,GA,@grafana/identity-access-team,2023-11-29,false,false,true +lokiQueryHints,GA,@grafana/observability-logs,2023-12-18,false,false,true +kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,2023-12-22,false,false,true +alertingPreviewUpgrade,preview,@grafana/alerting-squad,2024-01-03,false,true,false +enablePluginsTracingByDefault,experimental,@grafana/plugins-platform-backend,2024-01-09,false,true,false +cloudRBACRoles,experimental,@grafana/identity-access-team,2024-01-10,false,true,false +alertingQueryOptimization,GA,@grafana/alerting-squad,2024-01-10,false,false,false +newFolderPicker,experimental,@grafana/grafana-frontend-platform,2024-01-12,false,false,true +jitterAlertRules,GA,@grafana/alerting-squad,2024-01-17,false,true,false +jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,2024-01-17,false,true,false +onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,2024-01-22,false,false,false +alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,2024-01-22,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen_test.go b/pkg/services/featuremgmt/toggles_gen_test.go index c144cb8a46c..9dd117f976f 100644 --- a/pkg/services/featuremgmt/toggles_gen_test.go +++ b/pkg/services/featuremgmt/toggles_gen_test.go @@ -218,7 +218,6 @@ func generateCSV() string { "Owner", //string(flag.Owner), "Created", "requiresDevMode", //strconv.FormatBool(flag.RequiresDevMode), - "RequiresLicense", //strconv.FormatBool(flag.RequiresLicense), "RequiresRestart", //strconv.FormatBool(flag.RequiresRestart), "FrontendOnly", //strconv.FormatBool(flag.FrontendOnly), }); err != nil { @@ -239,7 +238,6 @@ func generateCSV() string { string(flag.Owner), dateFormatter(flag.Created), strconv.FormatBool(flag.RequiresDevMode), - strconv.FormatBool(flag.RequiresLicense), strconv.FormatBool(flag.RequiresRestart), strconv.FormatBool(flag.FrontendOnly), }); err != nil { diff --git a/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go b/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go index b462bc14ac1..1a7840d545c 100644 --- a/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go +++ b/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go @@ -30,7 +30,7 @@ func ReqCanAdminPlugins(cfg *setting.Cfg) func(rc *contextmodel.ReqContext) bool } } -func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg) error { +func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg, features featuremgmt.FeatureToggles) error { AppPluginsReader := ac.RoleRegistration{ Role: ac.RoleDTO{ Name: ac.FixedRolePrefix + "plugins.app:reader", @@ -69,7 +69,7 @@ func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg) error { } if !cfg.PluginAdminEnabled || - (cfg.PluginAdminExternalManageEnabled && !cfg.IsFeatureToggleEnabled(featuremgmt.FlagManagedPluginsInstall)) { + (cfg.PluginAdminExternalManageEnabled && !features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall)) { PluginsMaintainer.Grants = []string{} } diff --git a/pkg/services/sqlstore/database_config.go b/pkg/services/sqlstore/database_config.go index af20267f8fa..1b627d0515e 100644 --- a/pkg/services/sqlstore/database_config.go +++ b/pkg/services/sqlstore/database_config.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/go-sql-driver/mysql" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" @@ -45,7 +46,7 @@ type DatabaseConfig struct { TransactionRetries int } -func NewDatabaseConfig(cfg *setting.Cfg) (*DatabaseConfig, error) { +func NewDatabaseConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (*DatabaseConfig, error) { if cfg == nil { return nil, errors.New("cfg cannot be nil") } @@ -55,7 +56,7 @@ func NewDatabaseConfig(cfg *setting.Cfg) (*DatabaseConfig, error) { return nil, err } - if err := dbCfg.buildConnectionString(cfg); err != nil { + if err := dbCfg.buildConnectionString(cfg, features); err != nil { return nil, err } @@ -120,7 +121,7 @@ func (dbCfg *DatabaseConfig) readConfig(cfg *setting.Cfg) error { return nil } -func (dbCfg *DatabaseConfig) buildConnectionString(cfg *setting.Cfg) error { +func (dbCfg *DatabaseConfig) buildConnectionString(cfg *setting.Cfg, features featuremgmt.FeatureToggles) error { if dbCfg.ConnectionString != "" { return nil } @@ -154,8 +155,7 @@ func (dbCfg *DatabaseConfig) buildConnectionString(cfg *setting.Cfg) error { cnnstr += fmt.Sprintf("&transaction_isolation=%s", val) } - // nolint:staticcheck - if cfg.IsFeatureToggleEnabled(featuremgmt.FlagMysqlAnsiQuotes) { + if features != nil && features.IsEnabledGlobally(featuremgmt.FlagMysqlAnsiQuotes) { cnnstr += "&sql_mode='ANSI_QUOTES'" } diff --git a/pkg/services/sqlstore/database_config_test.go b/pkg/services/sqlstore/database_config_test.go index 9fba6b55a23..942cdc23561 100644 --- a/pkg/services/sqlstore/database_config_test.go +++ b/pkg/services/sqlstore/database_config_test.go @@ -5,10 +5,11 @@ import ( "net/url" "testing" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" ) type databaseConfigTest struct { @@ -109,7 +110,7 @@ func TestIntegrationSQLConnectionString(t *testing.T) { for _, testCase := range databaseConfigTestCases { t.Run(testCase.name, func(t *testing.T) { cfg := makeDatabaseTestConfig(t, testCase) - dbCfg, err := NewDatabaseConfig(cfg) + dbCfg, err := NewDatabaseConfig(cfg, testCase.features) require.Equal(t, testCase.err, err) if testCase.expConnStr != "" { assert.Equal(t, testCase.expConnStr, dbCfg.ConnectionString) diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index 7e7f74f1666..ef60e500799 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -154,9 +154,7 @@ func TestMigrations(t *testing.T) { desc: "with editors can admin", config: &setting.Cfg{ EditorsCanAdmin: true, - // nolint:staticcheck - IsFeatureToggleEnabled: func(key string) bool { return key == "accesscontrol" }, - Raw: ini.Empty(), + Raw: ini.Empty(), }, expectedRolePerms: map[string][]rawPermission{ "managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}}, @@ -262,9 +260,6 @@ func setupTestDB(t *testing.T) *xorm.Engine { mg := migrator.NewMigrator(x, &setting.Cfg{ Logger: log.New("acmigration.test"), Raw: ini.Empty(), - IsFeatureToggleEnabled: func(key string) bool { - return true - }, }) migrations := &migrations.OSSMigrations{} migrations.AddMigration(mg) diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index f7e9efa7ce2..e4602dada79 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -21,13 +21,14 @@ import ( // specifically added type OSSMigrations struct { + features featuremgmt.FeatureToggles } -func ProvideOSSMigrations() *OSSMigrations { - return &OSSMigrations{} +func ProvideOSSMigrations(features featuremgmt.FeatureToggles) *OSSMigrations { + return &OSSMigrations{features} } -func (*OSSMigrations) AddMigration(mg *Migrator) { +func (oss *OSSMigrations) AddMigration(mg *Migrator) { mg.AddCreateMigration() addUserMigrations(mg) addTempUserMigrations(mg) @@ -94,12 +95,8 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { AddExternalAlertmanagerToDatasourceMigration(mg) addFolderMigrations(mg) - // nolint:staticcheck - if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil { - // nolint:staticcheck - if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagExternalServiceAuth) { - oauthserver.AddMigration(mg) - } + if oss.features != nil && oss.features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) { + oauthserver.AddMigration(mg) } anonservice.AddMigration(mg) @@ -115,12 +112,8 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { ualert.CreateOrgMigratedKVStoreEntries(mg) // https://github.com/grafana/identity-access-team/issues/546: tracks removal of the feature toggle from the annotation permission migration - // nolint:staticcheck - if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil { - // nolint:staticcheck - if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAnnotationPermissionUpdate) { - accesscontrol.AddManagedDashboardAnnotationActionsMigration(mg) - } + if oss.features != nil && oss.features.IsEnabledGlobally(featuremgmt.FlagAnnotationPermissionUpdate) { + accesscontrol.AddManagedDashboardAnnotationActionsMigration(mg) } addKVStoreMySQLValueTypeLongTextMigration(mg) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index faf272ce9f3..334c72ad4f5 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -38,6 +38,7 @@ type ContextSessionKey struct{} type SQLStore struct { Cfg *setting.Cfg + features featuremgmt.FeatureToggles sqlxsession *session.SessionDB bus bus.Bus @@ -52,7 +53,10 @@ type SQLStore struct { recursiveQueriesMu sync.Mutex } -func ProvideService(cfg *setting.Cfg, migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { +func ProvideService(cfg *setting.Cfg, + features featuremgmt.FeatureToggles, + migrations registry.DatabaseMigrator, + bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { // This change will make xorm use an empty default schema for postgres and // by that mimic the functionality of how it was functioning before // xorm's changes above. @@ -61,9 +65,9 @@ func ProvideService(cfg *setting.Cfg, migrations registry.DatabaseMigrator, bus if err != nil { return nil, err } + s.features = features - // nolint:staticcheck - if err := s.Migrate(cfg.IsFeatureToggleEnabled(featuremgmt.FlagMigrationLocking)); err != nil { + if err := s.Migrate(features.IsEnabledGlobally(featuremgmt.FlagMigrationLocking)); err != nil { return nil, err } @@ -87,8 +91,8 @@ func ProvideService(cfg *setting.Cfg, migrations registry.DatabaseMigrator, bus return s, nil } -func ProvideServiceForTests(cfg *setting.Cfg, migrations registry.DatabaseMigrator) (*SQLStore, error) { - return initTestDB(cfg, migrations, InitTestDBOpt{EnsureDefaultOrgAndUser: true}) +func ProvideServiceForTests(cfg *setting.Cfg, features featuremgmt.FeatureToggles, migrations registry.DatabaseMigrator) (*SQLStore, error) { + return initTestDB(cfg, features, migrations, InitTestDBOpt{EnsureDefaultOrgAndUser: true}) } func newSQLStore(cfg *setting.Cfg, engine *xorm.Engine, @@ -239,7 +243,7 @@ func (ss *SQLStore) initEngine(engine *xorm.Engine) error { return nil } - dbCfg, err := NewDatabaseConfig(ss.Cfg) + dbCfg, err := NewDatabaseConfig(ss.Cfg, ss.features) if err != nil { return err } @@ -402,15 +406,11 @@ type InitTestDBOpt struct { FeatureFlags []string } -var featuresEnabledDuringTests = []string{ - featuremgmt.FlagPanelTitleSearch, - featuremgmt.FlagUnifiedStorage, -} - // InitTestDBWithMigration initializes the test DB given custom migrations. func InitTestDBWithMigration(t ITestDB, migration registry.DatabaseMigrator, opts ...InitTestDBOpt) *SQLStore { t.Helper() - store, err := initTestDB(setting.NewCfg(), migration, opts...) + features := getFeaturesForTesting(opts...) + store, err := initTestDB(setting.NewCfg(), features, migration, opts...) if err != nil { t.Fatalf("failed to initialize sql store: %s", err) } @@ -420,7 +420,9 @@ func InitTestDBWithMigration(t ITestDB, migration registry.DatabaseMigrator, opt // InitTestDB initializes the test DB. func InitTestDB(t ITestDB, opts ...InitTestDBOpt) *SQLStore { t.Helper() - store, err := initTestDB(setting.NewCfg(), &migrations.OSSMigrations{}, opts...) + features := getFeaturesForTesting(opts...) + + store, err := initTestDB(setting.NewCfg(), features, migrations.ProvideOSSMigrations(features), opts...) if err != nil { t.Fatalf("failed to initialize sql store: %s", err) } @@ -432,8 +434,26 @@ func InitTestDBWithCfg(t ITestDB, opts ...InitTestDBOpt) (*SQLStore, *setting.Cf return store, store.Cfg } +func getFeaturesForTesting(opts ...InitTestDBOpt) featuremgmt.FeatureToggles { + featureKeys := []any{ + featuremgmt.FlagPanelTitleSearch, + featuremgmt.FlagUnifiedStorage, + } + for _, opt := range opts { + if len(opt.FeatureFlags) > 0 { + for _, f := range opt.FeatureFlags { + featureKeys = append(featureKeys, f) + } + } + } + return featuremgmt.WithFeatures(featureKeys...) +} + //nolint:gocyclo -func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts ...InitTestDBOpt) (*SQLStore, error) { +func initTestDB(testCfg *setting.Cfg, + features featuremgmt.FeatureToggles, + migration registry.DatabaseMigrator, + opts ...InitTestDBOpt) (*SQLStore, error) { testSQLStoreMutex.Lock() defer testSQLStoreMutex.Unlock() @@ -441,14 +461,6 @@ func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts opts = []InitTestDBOpt{{EnsureDefaultOrgAndUser: false, FeatureFlags: []string{}}} } - features := make([]string, len(featuresEnabledDuringTests)) - copy(features, featuresEnabledDuringTests) - for _, opt := range opts { - if len(opt.FeatureFlags) > 0 { - features = append(features, opt.FeatureFlags...) - } - } - if testSQLStore == nil { dbType := migrator.SQLite @@ -460,14 +472,7 @@ func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts // set test db config cfg := setting.NewCfg() // nolint:staticcheck - cfg.IsFeatureToggleEnabled = func(key string) bool { - for _, enabledFeature := range features { - if enabledFeature == key { - return true - } - } - return false - } + cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally sec, err := cfg.Raw.NewSection("database") if err != nil { @@ -555,14 +560,7 @@ func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts } // nolint:staticcheck - testSQLStore.Cfg.IsFeatureToggleEnabled = func(key string) bool { - for _, enabledFeature := range features { - if enabledFeature == key { - return true - } - } - return false - } + testSQLStore.Cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally if err := testSQLStore.Dialect.TruncateDBTables(testSQLStore.GetEngine()); err != nil { return nil, err diff --git a/pkg/services/store/entity/server/config.go b/pkg/services/store/entity/server/config.go index cef38e756da..22bdba14646 100644 --- a/pkg/services/store/entity/server/config.go +++ b/pkg/services/store/entity/server/config.go @@ -41,7 +41,7 @@ func newConfig(cfg *setting.Cfg) *config { host := fmt.Sprintf("%s:%d", ip, port) return &config{ - enabled: true, // cfg.IsFeatureToggleEnabled(featuremgmt.FlagGrafanaStorageServer), + enabled: true, devMode: cfg.Env == setting.Dev, ip: ip, port: port, diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index c2d89e072e4..a66bef430c2 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -144,12 +144,6 @@ func (o *OSSImpl) Section(section string) Section { func (*OSSImpl) RegisterReloadHandler(string, ReloadHandler) {} -// Deprecated: use feature toggles -func (o *OSSImpl) IsFeatureToggleEnabled(name string) bool { - // nolint:staticcheck - return o.Cfg.IsFeatureToggleEnabled(name) -} - type keyValImpl struct { key *ini.Key } From 835ded5eec9137af3fd9e2849942285d6cf38e8f Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Sun, 28 Jan 2024 21:33:34 -0600 Subject: [PATCH 053/169] Histogram: Fix x-axis going past +Inf when rendering heatmap-rows frames (#81438) --- .../panel-histogram/histogram_tests.json | 203 ++++++++++++++++-- .../app/plugins/panel/histogram/Histogram.tsx | 17 +- 2 files changed, 192 insertions(+), 28 deletions(-) diff --git a/devenv/dev-dashboards/panel-histogram/histogram_tests.json b/devenv/dev-dashboards/panel-histogram/histogram_tests.json index 4399f6b6796..60ca7a6302b 100644 --- a/devenv/dev-dashboards/panel-histogram/histogram_tests.json +++ b/devenv/dev-dashboards/panel-histogram/histogram_tests.json @@ -59,7 +59,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -128,7 +129,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -197,7 +199,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -279,7 +282,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -341,7 +345,9 @@ }, "custom": { "align": "auto", - "displayMode": "auto", + "cellOptions": { + "type": "auto" + }, "inspect": false }, "mappings": [], @@ -356,7 +362,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -368,6 +375,7 @@ }, "id": 8, "options": { + "cellHeight": "sm", "footer": { "countRows": false, "fields": [], @@ -376,9 +384,15 @@ ], "show": false }, - "showHeader": true + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Height" + } + ] }, - "pluginVersion": "9.4.0-pre", + "pluginVersion": "10.4.0-pre", "targets": [ { "csvFileName": "weight_height.csv", @@ -393,6 +407,7 @@ "title": "Standalone transform - Height", "transformations": [ { + "disabled": true, "id": "filterFieldsByName", "options": { "include": { @@ -403,6 +418,7 @@ } }, { + "disabled": true, "id": "histogram", "options": { "combine": true, @@ -424,7 +440,9 @@ }, "custom": { "align": "auto", - "displayMode": "auto", + "cellOptions": { + "type": "auto" + }, "inspect": false }, "mappings": [], @@ -439,7 +457,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -451,6 +470,7 @@ }, "id": 9, "options": { + "cellHeight": "sm", "footer": { "countRows": false, "fields": [], @@ -461,7 +481,7 @@ }, "showHeader": true }, - "pluginVersion": "9.4.0-pre", + "pluginVersion": "10.4.0-pre", "targets": [ { "csvFileName": "weight_height.csv", @@ -527,7 +547,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [ { @@ -611,7 +632,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [ { @@ -695,7 +717,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [ { @@ -746,9 +769,157 @@ ], "title": "Implict xMin", "type": "histogram" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 33 + }, + "id": 14, + "options": { + "bucketOffset": 0, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + } + }, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"custom\": {\n \"resultType\": \"matrix\"\n },\n \"type\": \"heatmap-rows\",\n \"typeVersion\": [\n 0,\n 1\n ]\n },\n \"name\": \"0.005\",\n \"fields\": [\n {\n \"config\": {\n \"interval\": 1200000\n },\n \"name\": \"Time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.005\"\n },\n \"labels\": {\n \"le\": \"0.005\"\n },\n \"name\": \"0.005\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.01\"\n },\n \"labels\": {\n \"le\": \"0.01\"\n },\n \"name\": \"0.01\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.025\"\n },\n \"labels\": {\n \"le\": \"0.025\"\n },\n \"name\": \"0.025\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.05\"\n },\n \"labels\": {\n \"le\": \"0.05\"\n },\n \"name\": \"0.05\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.1\"\n },\n \"labels\": {\n \"le\": \"0.1\"\n },\n \"name\": \"0.1\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.25\"\n },\n \"labels\": {\n \"le\": \"0.25\"\n },\n \"name\": \"0.25\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"0.5\"\n },\n \"labels\": {\n \"le\": \"0.5\"\n },\n \"name\": \"0.5\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"1.0\"\n },\n \"labels\": {\n \"le\": \"1.0\"\n },\n \"name\": \"1.0\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"2.5\"\n },\n \"labels\": {\n \"le\": \"2.5\"\n },\n \"name\": \"2.5\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"5.0\"\n },\n \"labels\": {\n \"le\": \"5.0\"\n },\n \"name\": \"5.0\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"10.0\"\n },\n \"labels\": {\n \"le\": \"10.0\"\n },\n \"name\": \"10.0\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"25.0\"\n },\n \"labels\": {\n \"le\": \"25.0\"\n },\n \"name\": \"25.0\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"50.0\"\n },\n \"labels\": {\n \"le\": \"50.0\"\n },\n \"name\": \"50.0\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"100.0\"\n },\n \"labels\": {\n \"le\": \"100.0\"\n },\n \"name\": \"100.0\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n },\n {\n \"config\": {\n \"displayNameFromDS\": \"+Inf\"\n },\n \"labels\": {\n \"le\": \"+Inf\"\n },\n \"name\": \"+Inf\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1706456400000,\n 1706457600000,\n 1706458800000,\n 1706460000000,\n 1706461200000,\n 1706462400000,\n 1706463600000,\n 1706464800000,\n 1706466000000,\n 1706467200000,\n 1706468400000,\n 1706469600000,\n 1706470800000,\n 1706472000000,\n 1706473200000,\n 1706474400000,\n 1706475600000,\n 1706476800000,\n 1706478000000\n ],\n [\n 0.19357429718875502,\n 0.18072289156626506,\n 0.18313253012048192,\n 0.18955823293172688,\n 0.18634538152610441,\n 0.19518072289156624,\n 0.20080321285140562,\n 0.18313253012048192,\n 0.19678714859437751,\n 0.18795180722891563,\n 0.18473895582329317,\n 0.19357429718875502,\n 0.19116465863453813,\n 0.19196787148594374,\n 0.19437751004016063,\n 0.19759036144578312,\n 0.19839357429718874,\n 0.19357429718875502,\n 0.18634538152610441\n ],\n [\n 0.22248995983935738,\n 0.229718875502008,\n 0.22248995983935738,\n 0.22168674698795174,\n 0.2305220883534136,\n 0.21285140562248994,\n 0.2128514056224899,\n 0.22891566265060237,\n 0.22570281124497987,\n 0.22088353413654616,\n 0.22088353413654618,\n 0.21927710843373488,\n 0.21686746987951805,\n 0.22248995983935738,\n 0.21847389558232927,\n 0.21124497991967867,\n 0.216867469879518,\n 0.2200803212851405,\n 0.22329317269076304\n ],\n [\n 0.017670682730923704,\n 0.02329317269076303,\n 0.027309236947791138,\n 0.02168674698795181,\n 0.016867469879518093,\n 0.025702811244979917,\n 0.02008032128514059,\n 0.018473895582329314,\n 0.01124497991967871,\n 0.024899598393574307,\n 0.025702811244979862,\n 0.0208835341365462,\n 0.02409638554216864,\n 0.01927710843373498,\n 0.0208835341365462,\n 0.02248995983935742,\n 0.016867469879518093,\n 0.02008032128514059,\n 0.02329317269076303\n ],\n [\n 0,\n 0,\n 0.0008032128514056658,\n 0.0008032128514056658,\n 0,\n 0,\n 0,\n 0.0032128514056224966,\n 0,\n 0,\n 0.0024096385542168863,\n 0,\n 0.001606425702811276,\n 0,\n 0,\n 0.0024096385542168863,\n 0.001606425702811276,\n 0,\n 0.0008032128514056103\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665,\n 0.06666666666666665\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "heatmap-rows frame", + "type": "histogram" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "sum(rate(cortex_request_duration_seconds{container=~\"compactor\", route=~\"(debug_pprof|metrics|ready)\"}[4m0s]))" + }, + "properties": [ + { + "id": "unit", + "value": "s" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 33 + }, + "id": 15, + "options": { + "bucketOffset": 0, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + } + }, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"type\": \"heatmap-cells\",\n \"typeVersion\": [\n 0,\n 0\n ],\n \"executedQueryString\": \"Expr: sum(rate(cortex_request_duration_seconds{container=~\\\"compactor\\\", route=~\\\"(debug_pprof|metrics|ready)\\\"}[4m0s]))\\nStep: 1m0s\",\n \"preferredVisualisationType\": \"graph\"\n },\n \"fields\": [\n {\n \"name\": \"xMax\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\"\n },\n \"config\": {\n \"interval\": 60000\n }\n },\n {\n \"name\": \"yMin\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n },\n \"labels\": {},\n \"config\": {\n \"displayNameFromDS\": \"sum(rate(cortex_request_duration_seconds{container=~\\\"compactor\\\", route=~\\\"(debug_pprof|metrics|ready)\\\"}[4m0s]))\"\n }\n },\n {\n \"name\": \"yMax\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n },\n \"config\": {}\n },\n {\n \"name\": \"count\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\"\n },\n \"config\": {}\n },\n {\n \"name\": \"yLayout\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int8\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483460000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483520000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483580000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483640000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483700000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000,\n 1706483760000\n ],\n [\n 0.000012831061023768835,\n 0.000013992371264719713,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.00002566212204753767,\n 0.000039576402424652394,\n 0.00020529697638030136,\n 0.0002238779402355154,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.00048828125,\n 0.002532889755177753,\n 0.002762135864009951,\n 0.00390625,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.012048522073499537,\n 0.014328188175072986,\n 0.01703918332289465,\n 0.018581361171917516,\n 0.020263118041422026,\n 13.45434264405943,\n 0.000012831061023768835,\n 0.000013992371264719713,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.00002566212204753767,\n 0.000039576402424652394,\n 0.0002238779402355154,\n 0.000244140625,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00048828125,\n 0.0021298979153618314,\n 0.002532889755177753,\n 0.002762135864009951,\n 0.00390625,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.014328188175072986,\n 0.01703918332289465,\n 13.45434264405943,\n 0.000012831061023768835,\n 0.000013992371264719713,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.00002566212204753767,\n 0.000244140625,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.0021298979153618314,\n 0.002532889755177753,\n 0.002762135864009951,\n 0.00390625,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.014328188175072986,\n 0.01703918332289465,\n 13.45434264405943,\n 0.000012831061023768835,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.00002566212204753767,\n 0.000244140625,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.0021298979153618314,\n 0.002762135864009951,\n 0.00390625,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.014328188175072986,\n 0.01703918332289465,\n 13.45434264405943,\n 0.000012831061023768835,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.00002566212204753767,\n 0.000244140625,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.0005806675366224224,\n 0.0021298979153618314,\n 0.002762135864009951,\n 0.00390625,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.015625,\n 13.45434264405943,\n 0.000012831061023768835,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.00002566212204753767,\n 0.000244140625,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0003452669830012439,\n 0.0005806675366224224,\n 0.0021298979153618314,\n 0.002762135864009951,\n 0.00390625,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.015625,\n 13.45434264405943\n ],\n [\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.000023532269674803783,\n 0.000027984742529439426,\n 0.000043158372875155485,\n 0.0002238779402355154,\n 0.000244140625,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0003452669830012439,\n 0.0005324744788404579,\n 0.002762135864009951,\n 0.0030121305183748843,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.013139006488339287,\n 0.015625,\n 0.018581361171917516,\n 0.020263118041422026,\n 0.022097086912079608,\n 14.672064691274738,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.000023532269674803783,\n 0.000027984742529439426,\n 0.000043158372875155485,\n 0.000244140625,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0005324744788404579,\n 0.0023226701464896895,\n 0.002762135864009951,\n 0.0030121305183748843,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.013139006488339287,\n 0.015625,\n 0.018581361171917516,\n 14.672064691274738,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.000023532269674803783,\n 0.000027984742529439426,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0023226701464896895,\n 0.002762135864009951,\n 0.0030121305183748843,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.013139006488339287,\n 0.015625,\n 0.018581361171917516,\n 14.672064691274738,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.000023532269674803783,\n 0.000027984742529439426,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0023226701464896895,\n 0.0030121305183748843,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.013139006488339287,\n 0.015625,\n 0.018581361171917516,\n 14.672064691274738,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.000023532269674803783,\n 0.000027984742529439426,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0006332224387944383,\n 0.0023226701464896895,\n 0.0030121305183748843,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.01703918332289465,\n 14.672064691274738,\n 0.000013992371264719713,\n 0.0000152587890625,\n 0.000016639827463764308,\n 0.0000181458605194507,\n 0.000019788201212326197,\n 0.000021579186437577742,\n 0.000023532269674803783,\n 0.000027984742529439426,\n 0.00026623723942022893,\n 0.0002903337683112112,\n 0.00031661121939721915,\n 0.0003452669830012439,\n 0.00037651631479686053,\n 0.0006332224387944383,\n 0.0023226701464896895,\n 0.0030121305183748843,\n 0.004259795830723663,\n 0.004645340292979379,\n 0.005065779510355506,\n 0.005524271728019902,\n 0.0060242610367497685,\n 0.006569503244169644,\n 0.007164094087536493,\n 0.0078125,\n 0.008519591661447326,\n 0.009290680585958758,\n 0.010131559020711013,\n 0.011048543456039804,\n 0.012048522073499537,\n 0.01703918332289465,\n 14.672064691274738\n ],\n [\n 0.0044444444444444444,\n 0.017777777777777778,\n 0.0044444444444444444,\n 0.035555555555555556,\n 0.017777777777777778,\n 0.013333333333333332,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.017777777777777778,\n 0.03111111111111111,\n 0.013333333333333332,\n 0.05333333333333334,\n 0.035555555555555556,\n 0.022222222222222223,\n 0.022222222222222223,\n 0.022222222222222223,\n 0.017777777777777778,\n 0.013333333333333332,\n 0.013333333333333332,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.06666666666666667,\n 0.008888888888888889,\n 0.017777777777777778,\n 0.008888888888888889,\n 0.04,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.026666666666666665,\n 0.035555555555555556,\n 0.008888888888888889,\n 0.044444444444444446,\n 0.035555555555555556,\n 0.026666666666666665,\n 0.022222222222222223,\n 0.022222222222222223,\n 0.013333333333333332,\n 0.008888888888888889,\n 0.017777777777777778,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.06666666666666667,\n 0.008888888888888889,\n 0.017777777777777778,\n 0.017777777777777778,\n 0.04,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.017777777777777778,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.022222222222222223,\n 0.03111111111111111,\n 0.008888888888888889,\n 0.044444444444444446,\n 0.035555555555555556,\n 0.026666666666666665,\n 0.026666666666666665,\n 0.022222222222222223,\n 0.017777777777777778,\n 0.008888888888888889,\n 0.017777777777777778,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.06666666666666667,\n 0.008888888888888889,\n 0.017777777777777778,\n 0.0044444444444444444,\n 0.017777777777777778,\n 0.03111111111111111,\n 0.008888888888888889,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.013333333333333332,\n 0.008888888888888889,\n 0.008888888888888889,\n 0.008888888888888889,\n 0.022222222222222223,\n 0.013333333333333332,\n 0.008888888888888889,\n 0.044444444444444446,\n 0.04,\n 0.022222222222222223,\n 0.022222222222222223,\n 0.022222222222222223,\n 0.03111111111111111,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.017777777777777778,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.06666666666666667,\n 0.008888888888888889,\n 0.026666666666666665,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.022222222222222223,\n 0.008888888888888889,\n 0.013333333333333332,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.008888888888888889,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.008888888888888889,\n 0.026666666666666665,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.035555555555555556,\n 0.057777777777777775,\n 0.026666666666666665,\n 0.03111111111111111,\n 0.03111111111111111,\n 0.022222222222222223,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.013333333333333332,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.06666666666666667,\n 0.0044444444444444444,\n 0.026666666666666665,\n 0.0044444444444444444,\n 0.022222222222222223,\n 0.022222222222222223,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.013333333333333332,\n 0.017777777777777778,\n 0.013333333333333332,\n 0.008888888888888889,\n 0.035555555555555556,\n 0.05333333333333333,\n 0.022222222222222223,\n 0.026666666666666665,\n 0.035555555555555556,\n 0.022222222222222223,\n 0.008888888888888889,\n 0.02222222222222222,\n 0.008888888888888889,\n 0.0044444444444444444,\n 0.0044444444444444444,\n 0.06666666666666667\n ],\n [\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0,\n 0\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "heatmap-cells frame", + "type": "histogram" } ], - "schemaVersion": 37, + "refresh": "", + "schemaVersion": 39, "tags": [ "gdev", "panel-tests", @@ -765,6 +936,6 @@ "timezone": "", "title": "Panel Tests - Histogram", "uid": "UTv--wqMk", - "version": 7, + "version": 29, "weekStart": "" } diff --git a/public/app/plugins/panel/histogram/Histogram.tsx b/public/app/plugins/panel/histogram/Histogram.tsx index 3d51f1ef0a9..340e0489581 100644 --- a/public/app/plugins/panel/histogram/Histogram.tsx +++ b/public/app/plugins/panel/histogram/Histogram.tsx @@ -118,20 +118,13 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => { wantedMax = xScaleMax; } - let fullRangeMin = u.data[0][0]; let fullRangeMax = u.data[0][u.data[0].length - 1]; - // snap to bucket divisors... - - if (wantedMax === fullRangeMax) { - wantedMax += bucketSize; - } else { - wantedMax = incrRoundUp(wantedMax, bucketSize); - } - - if (wantedMin > fullRangeMin) { - wantedMin = incrRoundDn(wantedMin, bucketSize); - } + // isOrdinalX is when we have classic histograms, which are LE, ordinal X, and already have 0 dummy bucket prepended + // else we have calculated histograms which are GE and cardinal+linear X, and have no next dummy bucket appended + wantedMin = incrRoundUp(wantedMin, bucketSize); + wantedMax = + !isOrdinalX && wantedMax === fullRangeMax ? wantedMax + bucketSize : incrRoundDn(wantedMax, bucketSize); return [wantedMin, wantedMax]; }, From d34bd1dc9001b89c7108314a4e658bfbd3a2e7ff Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Sun, 28 Jan 2024 21:45:28 -0600 Subject: [PATCH 054/169] Chore: uPlot v1.6.29 (#81441) --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d5a64851cc2..b55bb484a54 100644 --- a/package.json +++ b/package.json @@ -397,7 +397,7 @@ "tinycolor2": "1.6.0", "tslib": "2.6.2", "tween-functions": "^1.2.0", - "uplot": "1.6.28", + "uplot": "1.6.29", "uuid": "9.0.1", "vendor": "link:./public/vendor", "visjs-network": "4.25.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 50a9402a6b2..fde8a9feeaa 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -58,7 +58,7 @@ "string-hash": "^1.1.3", "tinycolor2": "1.6.0", "tslib": "2.6.2", - "uplot": "1.6.28", + "uplot": "1.6.29", "xss": "^1.0.14" }, "devDependencies": { diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 41279d8b19d..14d3cf3f1dd 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -106,7 +106,7 @@ "slate-react": "0.22.10", "tinycolor2": "1.6.0", "tslib": "2.6.2", - "uplot": "1.6.28", + "uplot": "1.6.29", "uuid": "9.0.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 87ec5537850..80ccd77a9fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3231,7 +3231,7 @@ __metadata: tinycolor2: "npm:1.6.0" tslib: "npm:2.6.2" typescript: "npm:5.3.3" - uplot: "npm:1.6.28" + uplot: "npm:1.6.29" xss: "npm:^1.0.14" peerDependencies: react: ^17.0.0 || ^18.0.0 @@ -3819,7 +3819,7 @@ __metadata: tinycolor2: "npm:1.6.0" tslib: "npm:2.6.2" typescript: "npm:5.3.3" - uplot: "npm:1.6.28" + uplot: "npm:1.6.29" uuid: "npm:9.0.1" webpack: "npm:5.90.0" peerDependencies: @@ -17622,7 +17622,7 @@ __metadata: tslib: "npm:2.6.2" tween-functions: "npm:^1.2.0" typescript: "npm:5.3.3" - uplot: "npm:1.6.28" + uplot: "npm:1.6.29" uuid: "npm:9.0.1" vendor: "link:./public/vendor" visjs-network: "npm:4.25.0" @@ -29575,10 +29575,10 @@ __metadata: languageName: node linkType: hard -"uplot@npm:1.6.28": - version: 1.6.28 - resolution: "uplot@npm:1.6.28" - checksum: 6771289c45f2b8e8ad5d56abd8c92e7f5687244d75265f80957af857c2034b7b5e3128122262e5a546da2fc4437b600e56a058523eff1435b5bf6728ddc19612 +"uplot@npm:1.6.29": + version: 1.6.29 + resolution: "uplot@npm:1.6.29" + checksum: 0cda54a89cbe7f1ff5eb4a3e75bafa145a6af79e534cef6e8e4a1ace042b18a63ea56b25407d87fc7f073711ebb947a59de8068aa3576bc86b2514f1d058c187 languageName: node linkType: hard From 6a61c3da2e4424b27602702247e0b79e20521905 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Sun, 28 Jan 2024 22:36:28 -0600 Subject: [PATCH 055/169] TooltipPlugin2: Improve containment inside ancestors that hide overflow (#81442) --- .../uPlot/plugins/TooltipPlugin2.tsx | 114 +++++++++++------- 1 file changed, 70 insertions(+), 44 deletions(-) diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx index d7c2259b01a..f397bb8b739 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx @@ -146,20 +146,23 @@ export const TooltipPlugin2 = ({ let _plot = plot; let _isHovering = isHovering; + let _someSeriesIdx = false; let _isPinned = isPinned; let _style = style; + const updateHovering = () => { + _isHovering = closestSeriesIdx != null || (hoverMode === TooltipHoverMode.xAll && _someSeriesIdx); + }; + let offsetX = 0; let offsetY = 0; - let htmlEl = document.documentElement; - let winWidth = htmlEl.clientWidth - 16; - let winHeight = htmlEl.clientHeight - 16; - - window.addEventListener('resize', (e) => { - winWidth = htmlEl.clientWidth - 16; - winHeight = htmlEl.clientHeight - 16; - }); + let containRect = { + lft: 0, + top: 0, + rgt: screen.width, + btm: screen.height, + }; let selectedRange: TimeRange2 | null = null; let seriesIdxs: Array = plot?.cursor.idxs!.slice()!; @@ -290,6 +293,51 @@ export const TooltipPlugin2 = ({ } } }); + + const haltAncestorId = 'pageContent'; + const scrollbarWidth = 16; + + // if we're in a container that can clip the tooltip, we should try to stay within that rather than window edges + u.over.addEventListener( + 'mouseenter', + () => { + // clamp to viewport bounds + let htmlEl = document.documentElement; + let winWid = htmlEl.clientWidth - scrollbarWidth; + let winHgt = htmlEl.clientHeight - scrollbarWidth; + + let lft = 0, + top = 0, + rgt = winWid, + btm = winHgt; + + // find nearest scrollable container where overflow is not visible, (stop at #pageContent) + let par: HTMLElement | null = u.root; + + while (par != null && par.id !== haltAncestorId) { + let style = getComputedStyle(par); + let overflowX = style.getPropertyValue('overflow-x'); + let overflowY = style.getPropertyValue('overflow-y'); + + if (overflowX !== 'visible' || overflowY !== 'visible') { + let rect = par.getBoundingClientRect(); + lft = Math.max(rect.x, lft); + top = Math.max(rect.y, top); + rgt = Math.min(lft + rect.width, rgt); + btm = Math.min(top + rect.height, btm); + break; + } + + par = par.parentElement; + } + + containRect.lft = lft; + containRect.top = top; + containRect.rgt = rgt; + containRect.btm = btm; + }, + { capture: true } + ); }); config.addHook('setSelect', (u) => { @@ -372,44 +420,22 @@ export const TooltipPlugin2 = ({ yDrag = false; }); - // fires on data value hovers/unhovers (before setSeries) - config.addHook('setLegend', (u) => { - seriesIdxs = _plot?.cursor!.idxs!.slice()!; - - let hoveredSeriesIdx = seriesIdxs.findIndex((v, i) => i > 0 && v != null); - let _isHoveringNow = hoveredSeriesIdx !== -1; - - // setSeries may not fire if focus.prox is not set, so we set closestSeriesIdx here instead - if (hoverMode === TooltipHoverMode.xyOne) { - closestSeriesIdx = hoveredSeriesIdx; - } - - if (_isHoveringNow) { - // create - if (!_isHovering) { - _isHovering = true; - } - } else { - // destroy...TODO: debounce this - if (_isHovering) { - _isHovering = false; - } - } - - scheduleRender(); - }); - // fires on series focus/proximity changes // e.g. to highlight the hovered/closest series // TODO: we only need this for multi/all mode? config.addHook('setSeries', (u, seriesIdx) => { - // don't jiggle focused series styling when there's only one series - // const isMultiSeries = u.series.length > 2; - - // if (hoverModeRef.current === TooltipHoverMode.xAll && closestSeriesIdx !== seriesIdx) { closestSeriesIdx = seriesIdx; + updateHovering(); + scheduleRender(); + }); + + // fires on data value hovers/unhovers + config.addHook('setLegend', (u) => { + seriesIdxs = _plot?.cursor!.idxs!.slice()!; + _someSeriesIdx = seriesIdxs.some((v, i) => i > 0 && v != null); + + updateHovering(); scheduleRender(); - // } }); // fires on mousemoves @@ -435,25 +461,25 @@ export const TooltipPlugin2 = ({ let clientY = u.rect.top + top; if (offsetY !== 0) { - if (clientY + height < winHeight || clientY - height < 0) { + if (clientY + height < containRect.btm || clientY - height < 0) { offsetY = 0; } else if (offsetY !== -height) { offsetY = -height; } } else { - if (clientY + height > winHeight && clientY - height >= 0) { + if (clientY + height > containRect.btm && clientY - height >= 0) { offsetY = -height; } } if (offsetX !== 0) { - if (clientX + width < winWidth || clientX - width < 0) { + if (clientX + width < containRect.rgt || clientX - width < 0) { offsetX = 0; } else if (offsetX !== -width) { offsetX = -width; } } else { - if (clientX + width > winWidth && clientX - width >= 0) { + if (clientX + width > containRect.rgt && clientX - width >= 0) { offsetX = -width; } } From 61934588c579005de80c54b15f42f0d9449efd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Bedi?= Date: Mon, 29 Jan 2024 08:22:07 +0100 Subject: [PATCH 056/169] E2E: Skip flaky mysql test (#81388) --- e2e/various-suite/mysql.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/various-suite/mysql.spec.ts b/e2e/various-suite/mysql.spec.ts index 99746b008ca..e35f5c6fd1c 100644 --- a/e2e/various-suite/mysql.spec.ts +++ b/e2e/various-suite/mysql.spec.ts @@ -34,7 +34,7 @@ describe('MySQL datasource', () => { cy.wait('@datasets'); }); - it('code editor autocomplete should handle table name escaping/quoting', () => { + it.skip('code editor autocomplete should handle table name escaping/quoting', () => { e2e.components.RadioButton.container().filter(':contains("Code")').click(); cy.get('textarea').type('S{downArrow}{enter}'); From d8d9121b6d7eef73cbea6a9cd9d46440bbe25fb0 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:12:49 +0000 Subject: [PATCH 057/169] Tempo: Clean up older styles (#81247) * Clean up uber styles * Use theme spacing --- .betterer.results | 22 ------ .../TracePageHeader/SpanGraph/index.tsx | 20 ++++- .../TraceTimelineViewer/SpanBar.tsx | 2 +- .../SpanDetail/AccordianKeyValues.tsx | 10 ++- .../SpanDetail/AccordianLogs.tsx | 10 ++- .../SpanDetail/AccordianReferences.tsx | 12 ++- .../SpanDetail/AccordianText.tsx | 5 +- .../SpanDetail/KeyValuesTable.tsx | 11 ++- .../TraceTimelineViewer/SpanDetail/index.tsx | 24 ++++-- .../TimelineColumnResizer.tsx | 6 +- .../TimelineHeaderRow/TimelineHeaderRow.tsx | 7 +- .../TraceTimelineViewer/TimelineRow.tsx | 20 ++--- .../TraceView/components/common/Divider.tsx | 3 +- .../TraceView/components/uberUtilityStyles.ts | 73 ------------------- 14 files changed, 87 insertions(+), 138 deletions(-) delete mode 100644 public/app/features/explore/TraceView/components/uberUtilityStyles.ts diff --git a/.betterer.results b/.betterer.results index 79b6c42d071..cfc75962fc6 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3443,9 +3443,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "6"], [0, 0, 0, "Styles should be written using objects.", "7"] ], - "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineRow.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -3509,25 +3506,6 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/types/trace.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/features/explore/TraceView/components/uberUtilityStyles.ts:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"], - [0, 0, 0, "Styles should be written using objects.", "13"], - [0, 0, 0, "Styles should be written using objects.", "14"], - [0, 0, 0, "Styles should be written using objects.", "15"], - [0, 0, 0, "Styles should be written using objects.", "16"] - ], "public/app/features/explore/TraceView/createSpanLink.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx index f7c05fa723a..64595920cf5 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx @@ -12,18 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -import cx from 'classnames'; +import { css } from '@emotion/css'; import memoizeOne from 'memoize-one'; import * as React from 'react'; import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../../index'; import { TraceSpan, Trace } from '../../types'; -import { ubPb2, ubPx2, ubRelative } from '../../uberUtilityStyles'; import CanvasSpanGraph from './CanvasSpanGraph'; import TickLabels from './TickLabels'; import ViewingLayer from './ViewingLayer'; +const getStyles = () => { + return { + container: css({ + padding: '0 0.5rem 0.5rem 0.5rem', + }), + canvasContainer: css({ + position: 'relative', + }), + }; +}; + const DEFAULT_HEIGHT = 60; export const TIMELINE_TICK_INTERVAL = 4; @@ -62,15 +72,17 @@ export default class SpanGraph extends React.PureComponent { render() { const { height, trace, viewRange, updateNextViewRangeTime, updateViewRangeTime } = this.props; + const styles = getStyles(); + if (!trace) { return
; } const items = memoizedGetitems(trace); return ( -
+
-
+
- A segment on the critical path of the overall trace/request/workflow. + A segment on the critical path of the overall trace / request / workflow.
} > diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx index dfc31d55f8d..d03bbb0fdb0 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx @@ -21,13 +21,17 @@ import { Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; import { TraceKeyValuePair, TraceLink, TNil } from '../../types'; -import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles'; import * as markers from './AccordianKeyValues.markers'; import KeyValuesTable from './KeyValuesTable'; +import { alignIcon } from '.'; + export const getStyles = (theme: GrafanaTheme2) => { return { + container: css({ + textOverflow: 'ellipsis', + }), header: css` label: header; cursor: pointer; @@ -125,7 +129,7 @@ export default function AccordianKeyValues(props: AccordianKeyValuesProps) { const { className, data, highContrast, interactive, isOpen, label, linksGetter, onToggle } = props; const isEmpty = !Array.isArray(data) || !data.length; const styles = useStyles2(getStyles); - const iconCls = cx(uAlignIcon, { [styles.emptyIcon]: isEmpty }); + const iconCls = cx(alignIcon, { [styles.emptyIcon]: isEmpty }); let arrow: React.ReactNode | null = null; let headerProps: {} | null = null; if (interactive) { @@ -142,7 +146,7 @@ export default function AccordianKeyValues(props: AccordianKeyValuesProps) { } return ( -
+
{ return { AccordianLogs: css` @@ -55,6 +56,9 @@ const getStyles = (theme: GrafanaTheme2) => { label: AccordianLogsFooter; color: ${autoColor(theme, '#999')}; `, + AccordianKeyValuesItem: css({ + marginBottom: theme.spacing(0.5), + }), }; }; @@ -76,7 +80,7 @@ export default function AccordianLogs(props: AccordianLogsProps) { let headerProps: {} | null = null; if (interactive) { arrow = isOpen ? ( - + ) : ( ); @@ -100,7 +104,7 @@ export default function AccordianLogs(props: AccordianLogsProps) { { return { AccordianReferenceItem: css` @@ -59,6 +60,9 @@ const getStyles = (theme: GrafanaTheme2) => { label: AccordianReferencesFooter; color: ${autoColor(theme, '#999')}; `, + AccordianKeyValuesItem: css({ + marginBottom: theme.spacing(0.5), + }), ReferencesList: css` background: #fff; border: 1px solid #ddd; @@ -166,7 +170,7 @@ export function References(props: ReferenceItemProps) { {!!reference.tags?.length && (
+ ) : ( - + ); HeaderComponent = 'a'; headerProps = { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx index bcc2d6fa886..07ffdb08549 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx @@ -21,11 +21,12 @@ import { Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; import { TNil } from '../../types'; -import { uAlignIcon } from '../../uberUtilityStyles'; import { getStyles as getAccordianKeyValuesStyles } from './AccordianKeyValues'; import TextList from './TextList'; +import { alignIcon } from '.'; + const getStyles = (theme: GrafanaTheme2) => { return { header: css` @@ -70,7 +71,7 @@ export default function AccordianText(props: AccordianTextProps) { } = props; const isEmpty = !Array.isArray(data) || !data.length; const accordianKeyValuesStyles = useStyles2(getAccordianKeyValuesStyles); - const iconCls = cx(uAlignIcon, { [accordianKeyValuesStyles.emptyIcon]: isEmpty }); + const iconCls = cx(alignIcon, { [accordianKeyValuesStyles.emptyIcon]: isEmpty }); let arrow: React.ReactNode | null = null; let headerProps: {} | null = null; if (interactive) { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index 0860b1a62e6..6e80b457e27 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -22,7 +22,6 @@ import { Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; import CopyIcon from '../../common/CopyIcon'; import { TraceKeyValuePair, TraceLink, TNil } from '../../types'; -import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles'; import jsonMarkup from './jsonMarkup'; @@ -38,6 +37,9 @@ export const getStyles = (theme: GrafanaTheme2) => { max-height: 450px; overflow: auto; `, + table: css({ + width: '100%', + }), body: css` label: body; vertical-align: baseline; @@ -70,6 +72,9 @@ export const getStyles = (theme: GrafanaTheme2) => { vertical-align: middle; font-weight: bold; `, + jsonTable: css({ + display: 'inline-block', + }), }; }; @@ -109,13 +114,13 @@ export default function KeyValuesTable(props: KeyValuesTableProps) { const styles = useStyles2(getStyles); return (
- +
{data.map((row, i) => { const markup = { __html: jsonMarkup(parseIfComplexJson(row.value)), }; - const jsonTable =
; + const jsonTable =
; const links = linksGetter ? linksGetter(data, i) : null; let valueMarkup; if (links && links.length) { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx index e9dc3ba0c00..0bd2bbe0c26 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx @@ -32,7 +32,6 @@ import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STAT import { SpanLinkFunc, TNil } from '../../types'; import { SpanLinkDef, SpanLinkType } from '../../types/links'; import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan, TraceSpanReference } from '../../types/trace'; -import { uAlignIcon, ubM0, ubMb1, ubMy1, ubTxRightAlign } from '../../uberUtilityStyles'; import { formatDuration } from '../utils'; import AccordianKeyValues from './AccordianKeyValues'; @@ -54,6 +53,12 @@ const getStyles = (theme: GrafanaTheme2) => { listWrapper: css` overflow: hidden; `, + list: css({ + textAlign: 'right', + }), + operationName: css({ + margin: 0, + }), debugInfo: css` label: debugInfo; display: block; @@ -100,6 +105,9 @@ const getStyles = (theme: GrafanaTheme2) => { label: AccordianWarningsLabel; color: ${autoColor(theme, '#d36c08')}; `, + AccordianKeyValuesItem: css({ + marginBottom: theme.spacing(0.5), + }), Textarea: css` word-break: break-all; white-space: pre; @@ -110,6 +118,10 @@ const getStyles = (theme: GrafanaTheme2) => { }; }; +export const alignIcon = css({ + margin: '-0.2rem 0.25rem 0 0', +}); + export type TraceFlameGraphs = { [spanID: string]: DataFrame; }; @@ -310,14 +322,14 @@ export default function SpanDetail(props: SpanDetailProps) { return (
-

{operationName}

+

{operationName}

- +
{logLinkButton} {profileLinkButton} - +
{process.tags && ( - + {spanID} diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx index 6e58221feb4..d9d9f2c98ae 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx @@ -16,12 +16,10 @@ import { css } from '@emotion/css'; import cx from 'classnames'; import * as React from 'react'; -import { stylesFactory } from '@grafana/ui'; - import { TNil } from '../../types'; import DraggableManager, { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager'; -export const getStyles = stylesFactory(() => { +export const getStyles = () => { return { TimelineColumnResizer: css` left: 0; @@ -95,7 +93,7 @@ export const getStyles = stylesFactory(() => { } `, }; -}); +}; export type TimelineColumnResizerProps = { min: number; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx index 12af287f3ed..67534003dc6 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx @@ -13,14 +13,12 @@ // limitations under the License. import { css } from '@emotion/css'; -import cx from 'classnames'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; -import { ubFlex, ubPx2 } from '../../uberUtilityStyles'; import Ticks from '../Ticks'; import TimelineRow from '../TimelineRow'; import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; @@ -52,6 +50,9 @@ const getStyles = (theme: GrafanaTheme2) => { TimelineHeaderWrapper: css` label: TimelineHeaderWrapper; align-items: center; + display: flex; + padding-left: ${theme.spacing(1)}; + padding-right: ${theme.spacing(1)}; `, }; }; @@ -90,7 +91,7 @@ export default function TimelineHeaderRow(props: TimelineHeaderRowProps) { const styles = useStyles2(getStyles); return ( - +

Service & Operation

{ return { - flexRow: css` - display: flex; - flex: 0 1 auto; - flex-direction: row; - `, + row: css({ + display: 'flex', + flex: '0 1 auto', + flexDirection: 'row', + }), + rowCell: css({ + position: 'relative', + }), }; }; @@ -46,7 +47,7 @@ export default function TimelineRow(props: TTimelineRowProps) { const { children, className = '', ...rest } = props; const styles = useStyles2(getStyles); return ( -
+
{children}
); @@ -60,8 +61,9 @@ export function TimelineRowCell(props: TimelineRowCellProps) { const { children, className = '', width, style, ...rest } = props; const widthPercent = `${width * 100}%`; const mergedStyle = { ...style, flexBasis: widthPercent, maxWidth: widthPercent }; + const styles = useStyles2(getStyles); return ( -
+
{children}
); diff --git a/public/app/features/explore/TraceView/components/common/Divider.tsx b/public/app/features/explore/TraceView/components/common/Divider.tsx index 9eab10a61e2..edb43d863c0 100644 --- a/public/app/features/explore/TraceView/components/common/Divider.tsx +++ b/public/app/features/explore/TraceView/components/common/Divider.tsx @@ -26,7 +26,8 @@ const getStyles = (theme: GrafanaTheme2) => { display: block; height: 1px; width: 100%; - margin: 24px 0; + margin-top: ${theme.spacing(2)}; + margin-bottom: ${theme.spacing(0.5)}; clear: both; vertical-align: middle; position: relative; diff --git a/public/app/features/explore/TraceView/components/uberUtilityStyles.ts b/public/app/features/explore/TraceView/components/uberUtilityStyles.ts deleted file mode 100644 index d95db6e9c8c..00000000000 --- a/public/app/features/explore/TraceView/components/uberUtilityStyles.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { css } from '@emotion/css'; - -export const ubRelative = css` - position: relative; -`; - -export const ubMb1 = css` - margin-bottom: 0.25rem; -`; - -export const ubMy1 = css` - margin-top: 0.25rem; - margin-bottom: 0.25rem; -`; - -export const ubM0 = css` - margin: 0; -`; - -export const ubPx2 = css` - padding-left: 0.5rem; - padding-right: 0.5rem; -`; - -export const ubPb2 = css` - padding-bottom: 0.5rem; -`; - -export const ubFlex = css` - display: flex; -`; - -export const ubItemsCenter = css` - align-items: center; -`; - -export const ubItemsStart = css` - align-items: start; -`; - -export const ubFlexAuto = css` - flex: 1 1 auto; - min-width: 0; /* 1 */ - min-height: 0; /* 1 */ -`; - -export const ubTxRightAlign = css` - text-align: right; -`; - -export const ubInlineBlock = css` - display: inline-block; -`; - -export const uAlignIcon = css` - margin: -0.2rem 0.25rem 0 0; -`; - -export const uTxEllipsis = css` - text-overflow: ellipsis; -`; - -export const uWidth100 = css` - width: 100%; -`; - -export const uTxMuted = css` - color: #aaa; -`; - -export const ubJustifyEnd = css` - justify-content: flex-end; -`; From 9a4534a17c29060e5050da8484eff169de4f4ee3 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Mon, 29 Jan 2024 10:22:17 +0100 Subject: [PATCH 058/169] Transformations: Use the display name of the original y field for the predicted field of the regression analysis transformation. (#81332) Fix name of regression transformation field --- public/app/features/transformers/regression/regression.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/app/features/transformers/regression/regression.ts b/public/app/features/transformers/regression/regression.ts index 48799666750..80e70367284 100644 --- a/public/app/features/transformers/regression/regression.ts +++ b/public/app/features/transformers/regression/regression.ts @@ -9,6 +9,7 @@ import { FieldType, SynchronousDataTransformerInfo, fieldMatchers, + getFieldDisplayName, } from '@grafana/data'; export enum ModelType { @@ -42,6 +43,7 @@ export const RegressionTransformer: SynchronousDataTransformerInfo matchesY(f, frame, frames)); if (fy) { @@ -49,6 +51,7 @@ export const RegressionTransformer: SynchronousDataTransformerInfo matchesX(f, frame, frames)); if (fx) { xField = fx; + predictFromFrame = frame; break; } else { throw 'X and Y fields must be part of the same frame'; @@ -108,7 +111,7 @@ export const RegressionTransformer: SynchronousDataTransformerInfo result.predict(x - normalizationSubtrahend)), config: {}, From 34eb50dab3c8685af148e448ee905bae423e69a3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:27:35 +0000 Subject: [PATCH 059/169] Update dependency @floating-ui/react to v0.26.8 (#81435) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 32 ++++++++++++++++---------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index b55bb484a54..1ad223317d4 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,7 @@ "@emotion/css": "11.11.2", "@emotion/react": "11.11.3", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.26.7", + "@floating-ui/react": "0.26.8", "@glideapps/glide-data-grid": "^6.0.0", "@grafana-plugins/grafana-azure-monitor-datasource": "workspace:*", "@grafana-plugins/grafana-pyroscope-datasource": "workspace:*", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 14d3cf3f1dd..b1ddd1b202c 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -49,7 +49,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@emotion/react": "11.11.3", - "@floating-ui/react": "0.26.7", + "@floating-ui/react": "0.26.8", "@grafana/data": "10.4.0-pre", "@grafana/e2e-selectors": "10.4.0-pre", "@grafana/faro-web-sdk": "^1.3.6", diff --git a/yarn.lock b/yarn.lock index 80ccd77a9fe..f1891082876 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2816,39 +2816,39 @@ __metadata: languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.6.0": - version: 1.6.0 - resolution: "@floating-ui/dom@npm:1.6.0" +"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.6.1": + version: 1.6.1 + resolution: "@floating-ui/dom@npm:1.6.1" dependencies: "@floating-ui/core": "npm:^1.6.0" "@floating-ui/utils": "npm:^0.2.1" - checksum: fc559385a27feddff31bdb9f4c8bc05e12bda5aac4e44e7b5850a2af3d2960bf58d72034ccc3fc6c1bf5e69a97d6b5958ee73a885b5e7e7594dc4fa2c22ba47d + checksum: c010feb55be37662eb4cc8d0a22e21359c25247bbdcd9557617fd305cf08c8f020435b17e4b4f410201ba9abe3a0dd96b5c42d56e85f7a5e11e7d30b85afc116 languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.7": - version: 2.0.7 - resolution: "@floating-ui/react-dom@npm:2.0.7" +"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.8": + version: 2.0.8 + resolution: "@floating-ui/react-dom@npm:2.0.8" dependencies: - "@floating-ui/dom": "npm:^1.6.0" + "@floating-ui/dom": "npm:^1.6.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 15eb9ec493781253077e7e26c65c56586a1c92c414879c55d779cba5479d46b8897de06d61c831d03d24c24894257817fed9a515a4f2dc955666df5156dd757b + checksum: e57b2a498aecf8de0ec28adf434257fca7893bd9bd7e78b63ac98c63b29b9fc086fc175630154352f3610f5c4a0d329823837f4f6c235cc0459fde6417065590 languageName: node linkType: hard -"@floating-ui/react@npm:0.26.7": - version: 0.26.7 - resolution: "@floating-ui/react@npm:0.26.7" +"@floating-ui/react@npm:0.26.8": + version: 0.26.8 + resolution: "@floating-ui/react@npm:0.26.8" dependencies: - "@floating-ui/react-dom": "npm:^2.0.7" + "@floating-ui/react-dom": "npm:^2.0.8" "@floating-ui/utils": "npm:^0.2.1" tabbable: "npm:^6.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 85af7118b0dcd852cfe6e3299a5e467f9224fe240d95b8de7d754d1be4a103e27fbc889fbbc3bd6bee9c3b9799e54bc2640f60dd5aa5bbb4a355cb906512ec13 + checksum: 809af37e13baf67426be5542a8d608b7d9b904b5a386e7e5147da584dc154c3e977c67daefa4a3df1980dee07152a44f4997b8269132b732b62eb5e64b0a9809 languageName: node linkType: hard @@ -3690,7 +3690,7 @@ __metadata: "@babel/core": "npm:7.23.9" "@emotion/css": "npm:11.11.2" "@emotion/react": "npm:11.11.3" - "@floating-ui/react": "npm:0.26.7" + "@floating-ui/react": "npm:0.26.8" "@grafana/data": "npm:10.4.0-pre" "@grafana/e2e-selectors": "npm:10.4.0-pre" "@grafana/faro-web-sdk": "npm:^1.3.6" @@ -17310,7 +17310,7 @@ __metadata: "@emotion/eslint-plugin": "npm:11.11.0" "@emotion/react": "npm:11.11.3" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.26.7" + "@floating-ui/react": "npm:0.26.8" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana-plugins/grafana-azure-monitor-datasource": "workspace:*" "@grafana-plugins/grafana-pyroscope-datasource": "workspace:*" From 6f245121d09cc5cfb048334316c6e8bf60f8b635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Mon, 29 Jan 2024 11:31:49 +0200 Subject: [PATCH 060/169] Plugins: Fix colon in CallResource URL returning an error when creating plugin resource request (#79746) * Plugin: handle colon character in path url.Parse() does not handle the given input correctly when the input contains a colon character. The user will see the following error message when trying to use remote cluster in Elasticsearch: ``` level=warn msg="Failed for create plugin resource request" error="parse \"foo-*,*:foo-*/_mapping\": first path segment in URL cannot contain colon" traceID= ``` As far as I can tell, we only want to set the path here + rawquery so avoid url.Parse() altogether. * Add more tests --------- Co-authored-by: Giuseppe Guerra --- pkg/api/plugin_resource.go | 11 ++-- pkg/api/plugin_resource_test.go | 89 ++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/pkg/api/plugin_resource.go b/pkg/api/plugin_resource.go index 814f0329f8f..6f0530effcd 100644 --- a/pkg/api/plugin_resource.go +++ b/pkg/api/plugin_resource.go @@ -90,14 +90,11 @@ func (hs *HTTPServer) callPluginResourceWithDataSource(c *contextmodel.ReqContex func (hs *HTTPServer) pluginResourceRequest(c *contextmodel.ReqContext) (*http.Request, error) { clonedReq := c.Req.Clone(c.Req.Context()) rawURL := web.Params(c.Req)["*"] - if clonedReq.URL.RawQuery != "" { - rawURL += "?" + clonedReq.URL.RawQuery + + clonedReq.URL = &url.URL{ + Path: rawURL, + RawQuery: clonedReq.URL.RawQuery, } - urlPath, err := url.Parse(rawURL) - if err != nil { - return nil, err - } - clonedReq.URL = urlPath return clonedReq, nil } diff --git a/pkg/api/plugin_resource_test.go b/pkg/api/plugin_resource_test.go index 0d7d7fbe96b..9fb4532019c 100644 --- a/pkg/api/plugin_resource_test.go +++ b/pkg/api/plugin_resource_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "io" + "net/url" "path/filepath" "strings" "testing" @@ -53,22 +54,22 @@ func TestCallResource(t *testing.T) { coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), nil, &cloudwatch.CloudWatchService{}, nil, nil, nil, nil, nil, nil, nil, nil, testdatasource.ProvideService(), nil, nil, nil, nil, nil, nil) - textCtx := pluginsintegration.CreateIntegrationTestCtx(t, cfg, coreRegistry) + testCtx := pluginsintegration.CreateIntegrationTestCtx(t, cfg, coreRegistry) - pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(), textCtx.PluginStore, &datasources.FakeCacheService{}, + pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(), testCtx.PluginStore, &datasources.FakeCacheService{}, &datasources.FakeDataSourceService{}, pluginSettings.ProvideService(db.InitTestDB(t), fakeSecrets.NewFakeSecretsService()), nil, &pCfg) srv := SetupAPITestServer(t, func(hs *HTTPServer) { hs.Cfg = cfg hs.pluginContextProvider = pcp hs.QuotaService = quotatest.New(false, nil) - hs.pluginStore = textCtx.PluginStore - hs.pluginClient = textCtx.PluginClient + hs.pluginStore = testCtx.PluginStore + hs.pluginClient = testCtx.PluginClient hs.log = log.New("test") }) t.Run("Test successful response is received for valid request", func(t *testing.T) { - req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test", strings.NewReader("{ \"test\": true }")) + req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test", strings.NewReader(`{"test": "true"}`)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, @@ -88,6 +89,82 @@ func TestCallResource(t *testing.T) { require.NoError(t, resp.Body.Close()) require.Equal(t, 200, resp.StatusCode) }) + + t.Run("Test successful response is received for valid request with the colon character", func(t *testing.T) { + req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test-*,*:test-*/_mapping", strings.NewReader(`{"test": "true"}`)) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ + 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, + }), + }}) + resp, err := srv.SendJSON(req) + require.NoError(t, err) + + require.NoError(t, resp.Body.Close()) + require.Equal(t, 200, resp.StatusCode) + }) + + t.Run("CallResource plugin resource request is created correctly", func(t *testing.T) { + type testdataCallResourceTestResponse struct { + Message string `json:"message"` + Request struct { + URL url.URL + Body map[string]any `json:"body"` + } `json:"request"` + } + + for _, tc := range []struct { + name string + url string + exp func(t *testing.T, resp testdataCallResourceTestResponse) + }{ + { + name: "Simple URL", + url: "/api/plugins/grafana-testdata-datasource/resources/test", + exp: func(t *testing.T, resp testdataCallResourceTestResponse) { + require.Equal(t, "Hello world from test datasource!", resp.Message) + require.Equal(t, "/test", resp.Request.URL.Path) + require.Equal(t, "true", resp.Request.Body["test"]) + require.Len(t, resp.Request.Body, 1) + require.Empty(t, resp.Request.URL.RawQuery) + require.Empty(t, resp.Request.URL.Query()) + }, + }, + { + name: "URL with query params", + url: "/api/plugins/grafana-testdata-datasource/resources/test?test=true&a=b", + exp: func(t *testing.T, resp testdataCallResourceTestResponse) { + require.Equal(t, "Hello world from test datasource!", resp.Message) + require.Equal(t, "/test", resp.Request.URL.Path) + require.Equal(t, "test=true&a=b", resp.Request.URL.RawQuery) + query := resp.Request.URL.Query() + require.Equal(t, "true", query.Get("test")) + require.Equal(t, "b", query.Get("a")) + require.Len(t, query, 2) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + req := srv.NewPostRequest(tc.url, strings.NewReader(`{"test": "true"}`)) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ + 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, + }), + }}) + resp, err := srv.SendJSON(req) + require.NoError(t, err) + + var body testdataCallResourceTestResponse + require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + + tc.exp(t, body) + + require.NoError(t, resp.Body.Close()) + require.Equal(t, 200, resp.StatusCode) + }) + } + }) + pluginRegistry := fakes.NewFakePluginRegistry() require.NoError(t, pluginRegistry.Add(context.Background(), &plugins.Plugin{ JSONData: plugins.JSONData{ @@ -108,7 +185,7 @@ func TestCallResource(t *testing.T) { hs.Cfg = cfg hs.pluginContextProvider = pcp hs.QuotaService = quotatest.New(false, nil) - hs.pluginStore = textCtx.PluginStore + hs.pluginStore = testCtx.PluginStore hs.pluginClient = pc hs.log = log.New("test") }) From 2e1229b908fb4d65808b7dcdde3a43970264609f Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Mon, 29 Jan 2024 10:51:00 +0100 Subject: [PATCH 061/169] Alerting: Fix child provisioned polices not being rendered as provisioned (#81449) Fix child provisioned polices not being rendered as provisioned --- .../alerting/unified/components/notification-policies/Policy.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx index d4cb411e1bc..1266590af80 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx @@ -283,6 +283,7 @@ const Policy = (props: PolicyComponentProps) => { routesMatchingFilters={routesMatchingFilters} matchingInstancesPreview={matchingInstancesPreview} isAutoGenerated={isThisChildAutoGenerated} + provisioned={provisioned} /> ); })} From 26fa921547086bb0d26c6aa5a94bef3703a3d3a3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:33:55 +0000 Subject: [PATCH 062/169] Update dependency @swc/core to v1.3.107 --- package.json | 2 +- yarn.lock | 90 ++++++++++++++++++++++++++-------------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 1ad223317d4..cecbef2ead5 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@react-types/overlays": "3.8.4", "@react-types/shared": "3.22.0", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", - "@swc/core": "1.3.106", + "@swc/core": "1.3.107", "@swc/helpers": "0.5.3", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.3.0", diff --git a/yarn.lock b/yarn.lock index f1891082876..79350563148 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7650,90 +7650,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-darwin-arm64@npm:1.3.106" +"@swc/core-darwin-arm64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-arm64@npm:1.3.107" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-darwin-x64@npm:1.3.106" +"@swc/core-darwin-x64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-x64@npm:1.3.107" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.106" +"@swc/core-linux-arm-gnueabihf@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.107" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.106" +"@swc/core-linux-arm64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.106" +"@swc/core-linux-arm64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.106" +"@swc/core-linux-x64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-linux-x64-musl@npm:1.3.106" +"@swc/core-linux-x64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-musl@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.106" +"@swc/core-win32-arm64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.107" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.106" +"@swc/core-win32-ia32-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.107" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.106": - version: 1.3.106 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.106" +"@swc/core-win32-x64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.107" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:1.3.106, @swc/core@npm:^1.3.49": - version: 1.3.106 - resolution: "@swc/core@npm:1.3.106" +"@swc/core@npm:1.3.107, @swc/core@npm:^1.3.49": + version: 1.3.107 + resolution: "@swc/core@npm:1.3.107" dependencies: - "@swc/core-darwin-arm64": "npm:1.3.106" - "@swc/core-darwin-x64": "npm:1.3.106" - "@swc/core-linux-arm-gnueabihf": "npm:1.3.106" - "@swc/core-linux-arm64-gnu": "npm:1.3.106" - "@swc/core-linux-arm64-musl": "npm:1.3.106" - "@swc/core-linux-x64-gnu": "npm:1.3.106" - "@swc/core-linux-x64-musl": "npm:1.3.106" - "@swc/core-win32-arm64-msvc": "npm:1.3.106" - "@swc/core-win32-ia32-msvc": "npm:1.3.106" - "@swc/core-win32-x64-msvc": "npm:1.3.106" + "@swc/core-darwin-arm64": "npm:1.3.107" + "@swc/core-darwin-x64": "npm:1.3.107" + "@swc/core-linux-arm-gnueabihf": "npm:1.3.107" + "@swc/core-linux-arm64-gnu": "npm:1.3.107" + "@swc/core-linux-arm64-musl": "npm:1.3.107" + "@swc/core-linux-x64-gnu": "npm:1.3.107" + "@swc/core-linux-x64-musl": "npm:1.3.107" + "@swc/core-win32-arm64-msvc": "npm:1.3.107" + "@swc/core-win32-ia32-msvc": "npm:1.3.107" + "@swc/core-win32-x64-msvc": "npm:1.3.107" "@swc/counter": "npm:^0.1.1" "@swc/types": "npm:^0.1.5" peerDependencies: @@ -7762,7 +7762,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: dec4f621e0d93bb714699f20c4cb4b608dd5bd93d578e7ec29c9acbe582b1d1565caa6752348c3367a4ea0447cc871330ff4d245c25e58e8eeeb7c791aeab386 + checksum: 9f679f79cd4f89f20035a8f25945942f6d39a068ccec4a1c3b72d4266dddec8c4de38b89d65affd24768609f44da7217fe28e6498d2c95449ecbc76099e61bce languageName: node linkType: hard @@ -17363,7 +17363,7 @@ __metadata: "@reduxjs/toolkit": "npm:1.9.5" "@remix-run/router": "npm:^1.5.0" "@rtsao/plugin-proposal-class-properties": "npm:7.0.1-patch.1" - "@swc/core": "npm:1.3.106" + "@swc/core": "npm:1.3.107" "@swc/helpers": "npm:0.5.3" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.3.0" From 20e2f3006bda11096b57b9decbb4250cd4cd93d4 Mon Sep 17 00:00:00 2001 From: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:00:11 +0100 Subject: [PATCH 063/169] Add test for `TemporaryAlert` (#81416) --- .../src/TemporaryAlert.test.tsx | 28 +++++++++++++++++++ .../src/TemporaryAlert.tsx | 9 +++++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 packages/grafana-o11y-ds-frontend/src/TemporaryAlert.test.tsx diff --git a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.test.tsx b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.test.tsx new file mode 100644 index 00000000000..c8ae7b1c1a4 --- /dev/null +++ b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.test.tsx @@ -0,0 +1,28 @@ +import { act, render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TemporaryAlert } from './TemporaryAlert'; + +describe('TemporaryAlert', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + it('full component life cycle', async () => { + render(); + expect(screen.queryByTestId('data-testid Alert error')).not.toBeInTheDocument(); + + render(); + expect(screen.getByTestId('data-testid Alert error')).toBeInTheDocument(); + expect(screen.getByText('Error message')).toBeInTheDocument(); + + act(() => jest.runAllTimers()); + expect(screen.queryByTestId('data-testid Alert error')).not.toBeInTheDocument(); + expect(screen.queryByText('Error message')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx index 84a09bdee78..cabc3e3c493 100644 --- a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx +++ b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx @@ -27,7 +27,14 @@ const timeoutMap = { ['warning']: AlertTimeout.Warning, }; -export const TemporaryAlert = (props: { severity: AlertVariant; text: string }) => { +type AlertProps = { + // Severity of the alert. Controls the style of the alert (e.g., background color) + severity: AlertVariant; + // Displayed message. If set to empty string, the alert is not displayed + text: string; +}; + +export const TemporaryAlert = (props: AlertProps) => { const style = getStyle(useTheme2()); const [visible, setVisible] = useState(false); const [timer, setTimer] = useState(); From 34e88077aaa5bc1a3b7ef2ae7e507d070ff315e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:56:34 +0000 Subject: [PATCH 064/169] Update dependency @types/node to v20.11.10 --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- yarn.lock | 22 +++++++++---------- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index cecbef2ead5..577d30cc154 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "@types/lucene": "^2", "@types/marked": "5.0.2", "@types/mousetrap": "1.6.15", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "@types/node-forge": "^1", "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.4", "@types/papaparse": "5.3.14", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index fde8a9feeaa..fc33192de82 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -76,7 +76,7 @@ "@types/jquery": "3.5.29", "@types/lodash": "4.14.202", "@types/marked": "5.0.2", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "@types/papaparse": "5.3.14", "@types/react": "18.2.48", "@types/react-dom": "18.2.18", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index ebf0d74d412..aa2e97d5d1a 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "25.0.7", "@rollup/plugin-node-resolve": "15.2.3", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "esbuild": "0.18.12", "rimraf": "5.0.5", "rollup": "2.79.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index b1ddd1b202c..25cbcc9069c 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -140,7 +140,7 @@ "@types/jquery": "3.5.29", "@types/lodash": "4.14.202", "@types/mock-raf": "1.0.6", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "@types/prismjs": "1.26.3", "@types/react": "18.2.48", "@types/react-beautiful-dnd": "13.1.8", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 1285b24d4ac..7b8b5729dfa 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -29,7 +29,7 @@ "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.11", "@types/lodash": "4.14.202", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "@types/prismjs": "1.26.3", "@types/react": "18.2.48", "@types/testing-library__jest-dom": "5.14.9", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 262ee8e01cf..f05a62b106e 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -25,7 +25,7 @@ "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.11", "@types/lodash": "4.14.202", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "@types/react": "18.2.48", "@types/testing-library__jest-dom": "5.14.9", "@types/uuid": "9.0.8", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 662e3dfbd12..154a8c9bec7 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -47,7 +47,7 @@ "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.11", "@types/lodash": "4.14.202", - "@types/node": "20.11.7", + "@types/node": "20.11.10", "@types/prismjs": "1.26.3", "@types/react": "18.2.48", "@types/react-dom": "18.2.18", diff --git a/yarn.lock b/yarn.lock index 79350563148..84b3455783c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2949,7 +2949,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.11" "@types/lodash": "npm:4.14.202" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" "@types/prismjs": "npm:1.26.3" "@types/react": "npm:18.2.48" "@types/testing-library__jest-dom": "npm:5.14.9" @@ -3027,7 +3027,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.11" "@types/lodash": "npm:4.14.202" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" "@types/react": "npm:18.2.48" "@types/testing-library__jest-dom": "npm:5.14.9" "@types/uuid": "npm:9.0.8" @@ -3122,7 +3122,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.11" "@types/lodash": "npm:4.14.202" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" "@types/prismjs": "npm:1.26.3" "@types/react": "npm:18.2.48" "@types/react-dom": "npm:18.2.18" @@ -3195,7 +3195,7 @@ __metadata: "@types/jquery": "npm:3.5.29" "@types/lodash": "npm:4.14.202" "@types/marked": "npm:5.0.2" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" "@types/papaparse": "npm:5.3.14" "@types/react": "npm:18.2.48" "@types/react-dom": "npm:18.2.18" @@ -3257,7 +3257,7 @@ __metadata: "@grafana/tsconfig": "npm:^1.2.0-rc1" "@rollup/plugin-commonjs": "npm:25.0.7" "@rollup/plugin-node-resolve": "npm:15.2.3" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" esbuild: "npm:0.18.12" rimraf: "npm:5.0.5" rollup: "npm:2.79.1" @@ -3731,7 +3731,7 @@ __metadata: "@types/jquery": "npm:3.5.29" "@types/lodash": "npm:4.14.202" "@types/mock-raf": "npm:1.0.6" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" "@types/prismjs": "npm:1.26.3" "@types/react": "npm:18.2.48" "@types/react-beautiful-dnd": "npm:13.1.8" @@ -8882,12 +8882,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:20.11.7, @types/node@npm:>=13.7.0": - version: 20.11.7 - resolution: "@types/node@npm:20.11.7" +"@types/node@npm:*, @types/node@npm:20.11.10, @types/node@npm:>=13.7.0": + version: 20.11.10 + resolution: "@types/node@npm:20.11.10" dependencies: undici-types: "npm:~5.26.4" - checksum: ff0428c093987f1f6e3400b91450077cf006d7f0e4ff316e20636218562c53761565ceb06bd7a161badf46e6884d78b554c7b1c3a7717cf5cb8c97f461ecd754 + checksum: 41ca9c7e7c95bf070ad747ab266f267f41950e01f5ddda739d0665c2ed39acc45cce46f78e33ffa2bf2dacd74a8493044b967c86358017dacce12958e49db664 languageName: node linkType: hard @@ -17395,7 +17395,7 @@ __metadata: "@types/lucene": "npm:^2" "@types/marked": "npm:5.0.2" "@types/mousetrap": "npm:1.6.15" - "@types/node": "npm:20.11.7" + "@types/node": "npm:20.11.10" "@types/node-forge": "npm:^1" "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.4" "@types/papaparse": "npm:5.3.14" From a83e01918a6fc5f20a05dcf5baeb21cee95b99ef Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:19:34 +0100 Subject: [PATCH 065/169] Loki: Remove dependecy on core and move to `@grafana/o11y-ds-frontend` (#81284) * Loki: Remove dependecy on core and move to o11y-ds-frontend * Fix ctr -> ctrl * Remove test helpers --- .../LocalStorageValueProvider.tsx | 49 ++++ .../src/combineResponses.test.ts | 215 +++++++++++++++++- .../src/combineResponses.ts | 2 +- .../grafana-o11y-ds-frontend/src/index.ts | 3 + .../grafana-o11y-ds-frontend/src/store.ts | 64 ++++++ public/app/features/explore/state/query.ts | 3 +- .../plugins/datasource/loki/querySplitting.ts | 2 +- .../components/LabelBrowserModal.tsx | 2 +- .../components/LokiQueryBuilder.test.tsx | 4 +- .../LokiQueryBuilderContainer.test.tsx | 4 +- .../components/LokiQueryCodeEditor.tsx | 3 +- .../components/QueryPatternsModal.tsx | 5 +- 12 files changed, 342 insertions(+), 14 deletions(-) create mode 100644 packages/grafana-o11y-ds-frontend/src/LocalStorageValueProvider/LocalStorageValueProvider.tsx rename public/app/features/logs/response.test.ts => packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts (75%) rename public/app/features/logs/response.ts => packages/grafana-o11y-ds-frontend/src/combineResponses.ts (99%) create mode 100644 packages/grafana-o11y-ds-frontend/src/store.ts diff --git a/packages/grafana-o11y-ds-frontend/src/LocalStorageValueProvider/LocalStorageValueProvider.tsx b/packages/grafana-o11y-ds-frontend/src/LocalStorageValueProvider/LocalStorageValueProvider.tsx new file mode 100644 index 00000000000..b81935d4337 --- /dev/null +++ b/packages/grafana-o11y-ds-frontend/src/LocalStorageValueProvider/LocalStorageValueProvider.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react'; + +import { store } from '../store'; + +export interface Props { + storageKey: string; + defaultValue: T; + children: (value: T, onSaveToStore: (value: T) => void, onDeleteFromStore: () => void) => React.ReactNode; +} + +export const LocalStorageValueProvider = (props: Props) => { + const { children, storageKey, defaultValue } = props; + + const [state, setState] = useState({ value: store.getObject(props.storageKey, props.defaultValue) }); + + useEffect(() => { + const onStorageUpdate = (v: StorageEvent) => { + if (v.key === storageKey) { + setState({ value: store.getObject(props.storageKey, props.defaultValue) }); + } + }; + + window.addEventListener('storage', onStorageUpdate); + + return () => { + window.removeEventListener('storage', onStorageUpdate); + }; + }); + + const onSaveToStore = (value: T) => { + try { + store.setObject(storageKey, value); + } catch (error) { + console.error(error); + } + setState({ value }); + }; + + const onDeleteFromStore = () => { + try { + store.delete(storageKey); + } catch (error) { + console.log(error); + } + setState({ value: defaultValue }); + }; + + return <>{children(state.value, onSaveToStore, onDeleteFromStore)}; +}; diff --git a/public/app/features/logs/response.test.ts b/packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts similarity index 75% rename from public/app/features/logs/response.test.ts rename to packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts index 1c96656cea7..9438c1e20f8 100644 --- a/public/app/features/logs/response.test.ts +++ b/packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts @@ -1,7 +1,15 @@ -import { DataQueryResponse, LoadingState, PanelData, QueryResultMetaStat, getDefaultTimeRange } from '@grafana/data'; -import { getMockFrames } from 'app/plugins/datasource/loki/__mocks__/frames'; +import { + DataFrame, + DataFrameType, + DataQueryResponse, + FieldType, + LoadingState, + PanelData, + QueryResultMetaStat, + getDefaultTimeRange, +} from '@grafana/data'; -import { cloneQueryResponse, combinePanelData, combineResponses } from './response'; +import { cloneQueryResponse, combinePanelData, combineResponses } from './combineResponses'; describe('cloneQueryResponse', () => { const { logFrameA } = getMockFrames(); @@ -489,3 +497,204 @@ describe('combinePanelData', () => { }); }); }); + +export function getMockFrames() { + const logFrameA: DataFrame = { + refId: 'A', + fields: [ + { + name: 'Time', + type: FieldType.time, + config: {}, + values: [3, 4], + }, + { + name: 'Line', + type: FieldType.string, + config: {}, + values: ['line1', 'line2'], + }, + { + name: 'labels', + type: FieldType.other, + config: {}, + values: [ + { + label: 'value', + }, + { + otherLabel: 'other value', + }, + ], + }, + { + name: 'tsNs', + type: FieldType.string, + config: {}, + values: ['3000000', '4000000'], + }, + { + name: 'id', + type: FieldType.string, + config: {}, + values: ['id1', 'id2'], + }, + ], + meta: { + custom: { + frameType: 'LabeledTimeValues', + }, + stats: [ + { displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 11 }, + { displayName: 'Ingester: total reached', value: 1 }, + ], + }, + length: 2, + }; + + const logFrameB: DataFrame = { + refId: 'A', + fields: [ + { + name: 'Time', + type: FieldType.time, + config: {}, + values: [1, 2], + }, + { + name: 'Line', + type: FieldType.string, + config: {}, + values: ['line3', 'line4'], + }, + { + name: 'labels', + type: FieldType.other, + config: {}, + values: [ + { + otherLabel: 'other value', + }, + ], + }, + { + name: 'tsNs', + type: FieldType.string, + config: {}, + values: ['1000000', '2000000'], + }, + { + name: 'id', + type: FieldType.string, + config: {}, + values: ['id3', 'id4'], + }, + ], + meta: { + custom: { + frameType: 'LabeledTimeValues', + }, + stats: [ + { displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 22 }, + { displayName: 'Ingester: total reached', value: 2 }, + ], + }, + length: 2, + }; + + const metricFrameA: DataFrame = { + refId: 'A', + fields: [ + { + name: 'Time', + type: FieldType.time, + config: {}, + values: [3000000, 4000000], + }, + { + name: 'Value', + type: FieldType.number, + config: {}, + values: [5, 4], + labels: { + level: 'debug', + }, + }, + ], + meta: { + type: DataFrameType.TimeSeriesMulti, + stats: [ + { displayName: 'Ingester: total reached', value: 1 }, + { displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 11 }, + ], + }, + length: 2, + }; + + const metricFrameB: DataFrame = { + refId: 'A', + fields: [ + { + name: 'Time', + type: FieldType.time, + config: {}, + values: [1000000, 2000000], + }, + { + name: 'Value', + type: FieldType.number, + config: {}, + values: [6, 7], + labels: { + level: 'debug', + }, + }, + ], + meta: { + type: DataFrameType.TimeSeriesMulti, + stats: [ + { displayName: 'Ingester: total reached', value: 2 }, + { displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 22 }, + ], + }, + length: 2, + }; + + const metricFrameC: DataFrame = { + refId: 'A', + name: 'some-time-series', + fields: [ + { + name: 'Time', + type: FieldType.time, + config: {}, + values: [3000000, 4000000], + }, + { + name: 'Value', + type: FieldType.number, + config: {}, + values: [6, 7], + labels: { + level: 'error', + }, + }, + ], + meta: { + type: DataFrameType.TimeSeriesMulti, + stats: [ + { displayName: 'Ingester: total reached', value: 2 }, + { displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 33 }, + ], + }, + length: 2, + }; + + return { + logFrameA, + logFrameB, + metricFrameA, + metricFrameB, + metricFrameC, + }; +} diff --git a/public/app/features/logs/response.ts b/packages/grafana-o11y-ds-frontend/src/combineResponses.ts similarity index 99% rename from public/app/features/logs/response.ts rename to packages/grafana-o11y-ds-frontend/src/combineResponses.ts index fd09c052d66..f4133a7fc22 100644 --- a/public/app/features/logs/response.ts +++ b/packages/grafana-o11y-ds-frontend/src/combineResponses.ts @@ -73,7 +73,7 @@ function combineFrames(dest: DataFrame, source: DataFrame) { } const TOTAL_BYTES_STAT = 'Summary: total bytes processed'; - +// This is specific for Loki function getCombinedMetadataStats( destStats: QueryResultMetaStat[], sourceStats: QueryResultMetaStat[] diff --git a/packages/grafana-o11y-ds-frontend/src/index.ts b/packages/grafana-o11y-ds-frontend/src/index.ts index 18bd6e95419..c2f7e931a27 100644 --- a/packages/grafana-o11y-ds-frontend/src/index.ts +++ b/packages/grafana-o11y-ds-frontend/src/index.ts @@ -13,3 +13,6 @@ export * from './TraceToLogs/TraceToLogsSettings'; export * from './TraceToMetrics/TraceToMetricsSettings'; export * from './TraceToProfiles/TraceToProfilesSettings'; export * from './utils'; +export * from './store'; +export * from './LocalStorageValueProvider/LocalStorageValueProvider'; +export * from './combineResponses'; diff --git a/packages/grafana-o11y-ds-frontend/src/store.ts b/packages/grafana-o11y-ds-frontend/src/store.ts new file mode 100644 index 00000000000..3942f408a73 --- /dev/null +++ b/packages/grafana-o11y-ds-frontend/src/store.ts @@ -0,0 +1,64 @@ +type StoreValue = string | number | boolean | null; + +export class Store { + get(key: string) { + return window.localStorage[key]; + } + + set(key: string, value: StoreValue) { + window.localStorage[key] = value; + } + + getBool(key: string, def: boolean): boolean { + if (def !== void 0 && !this.exists(key)) { + return def; + } + return window.localStorage[key] === 'true'; + } + + getObject(key: string): T | undefined; + getObject(key: string, def: T): T; + getObject(key: string, def?: T) { + let ret = def; + if (this.exists(key)) { + const json = window.localStorage[key]; + try { + ret = JSON.parse(json); + } catch (error) { + console.error(`Error parsing store object: ${key}. Returning default: ${def}. [${error}]`); + } + } + return ret; + } + + /* Returns true when successfully stored, throws error if not successfully stored */ + setObject(key: string, value: unknown) { + let json; + try { + json = JSON.stringify(value); + } catch (error) { + throw new Error(`Could not stringify object: ${key}. [${error}]`); + } + try { + this.set(key, json); + } catch (error) { + // Likely hitting storage quota + const errorToThrow = new Error(`Could not save item in localStorage: ${key}. [${error}]`); + if (error instanceof Error) { + errorToThrow.name = error.name; + } + throw errorToThrow; + } + return true; + } + + exists(key: string) { + return window.localStorage[key] !== void 0; + } + + delete(key: string) { + window.localStorage.removeItem(key); + } +} + +export const store = new Store(); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index a78152b9d11..47f58b0ebbc 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -22,6 +22,7 @@ import { SupplementaryQueryType, toLegacyResponseData, } from '@grafana/data'; +import { combinePanelData } from '@grafana/o11y-ds-frontend'; import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import store from 'app/core/store'; @@ -39,7 +40,6 @@ import { import { getShiftedTimeRange } from 'app/core/utils/timePicker'; import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils'; import { infiniteScrollRefId } from 'app/features/logs/logsModel'; -import { combinePanelData } from 'app/features/logs/response'; import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { @@ -764,6 +764,7 @@ export const runLoadMoreLogsQueries = createAsyncThunk { }); }); }); + +const getSelectParent = (input: HTMLElement) => + input.parentElement?.parentElement?.parentElement?.parentElement?.parentElement; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx index 2ff57e8f5c0..fd89631554c 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx @@ -1,7 +1,6 @@ import { render, screen, waitFor, findAllByRole } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { getSelectParent } from 'test/helpers/selectOptionInTest'; import { createLokiDatasource } from '../../__mocks__/datasource'; @@ -138,3 +137,6 @@ async function addOperation(section: string, op: string) { // anywhere when debugging so not sure what style is it picking up. await userEvent.click(opItem, { pointerEventsCheck: 0 }); } + +const getSelectParent = (input: HTMLElement) => + input.parentElement?.parentElement?.parentElement?.parentElement?.parentElement; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx index 7e4fcc2ce3c..5db5313f985 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx @@ -4,7 +4,6 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { useStyles2, HorizontalGroup, IconButton, Tooltip, Icon } from '@grafana/ui'; -import { getModKey } from 'app/core/utils/browser'; import { testIds } from '../../components/LokiQueryEditor'; import { LokiQueryField } from '../../components/LokiQueryField'; @@ -57,7 +56,7 @@ export function LokiQueryCodeEditor({ size="xs" tooltip="Format query" /> - + diff --git a/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx b/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx index f2dbd821133..385b032b05e 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx @@ -2,10 +2,9 @@ import { css } from '@emotion/css'; import { capitalize } from 'lodash'; import React, { useMemo, useState } from 'react'; -import { CoreApp, DataQuery, GrafanaTheme2 } from '@grafana/data'; +import { CoreApp, DataQuery, GrafanaTheme2, getNextRefId } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui'; -import { getNextRefIdChar } from 'app/core/utils/query'; import { LokiQuery } from '../../types'; import { lokiQueryModeller } from '../LokiQueryModeller'; @@ -52,7 +51,7 @@ export const QueryPatternsModal = (props: Props) => { if (hasNewQueryOption && selectAsNewQuery) { onAddQuery({ ...query, - refId: getNextRefIdChar(queries ?? [query]), + refId: getNextRefId(queries ?? [query]), expr: lokiQueryModeller.renderQuery(visualQuery.query), }); } else { From df05c79d912203cb5ac364d22e5078bc4ad8d9cb Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Mon, 29 Jan 2024 11:24:05 +0100 Subject: [PATCH 066/169] Infinite scrolling: Add X-Query-Tag header (#81089) --- .../dataquery/x/LokiDataQuery_types.gen.ts | 1 + pkg/tsdb/loki/api.go | 2 ++ pkg/tsdb/loki/api_test.go | 25 +++++++++++++++++++ .../kinds/dataquery/types_dataquery_gen.go | 7 +++--- pkg/tsdb/loki/types.go | 9 ++++--- public/app/features/explore/state/query.ts | 2 ++ .../app/plugins/datasource/loki/dataquery.cue | 2 +- .../plugins/datasource/loki/dataquery.gen.ts | 1 + 8 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts index 328006a0467..a89814adb3b 100644 --- a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts @@ -26,6 +26,7 @@ export enum LokiQueryType { export enum SupportingQueryType { DataSample = 'dataSample', + InfiniteScroll = 'infiniteScroll', LogsSample = 'logsSample', LogsVolume = 'logsVolume', } diff --git a/pkg/tsdb/loki/api.go b/pkg/tsdb/loki/api.go index 6e6568ae79e..b7564897184 100644 --- a/pkg/tsdb/loki/api.go +++ b/pkg/tsdb/loki/api.go @@ -332,6 +332,8 @@ func getSupportingQueryHeaderValue(req *http.Request, supportingQueryType Suppor value = "logsample" case SupportingQueryDataSample: value = "datasample" + case SupportingQueryInfiniteScroll: + value = "infinitescroll" default: //ignore } diff --git a/pkg/tsdb/loki/api_test.go b/pkg/tsdb/loki/api_test.go index cbe964dbbe9..c510fd78d01 100644 --- a/pkg/tsdb/loki/api_test.go +++ b/pkg/tsdb/loki/api_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery" "github.com/stretchr/testify/require" ) @@ -82,6 +83,30 @@ func TestApiLogVolume(t *testing.T) { }) } +func TestInfiniteScroll(t *testing.T) { + response := []byte(` + { + "status": "success", + "data": { + "resultType" : "matrix", + "result": [] + } + } + `) + + t.Run("infinite scrolling queries should set infinite scroll http header", func(t *testing.T) { + called := false + api := makeMockedAPI(200, "application/json", response, func(req *http.Request) { + called = true + require.Equal(t, "Source=infinitescroll", req.Header.Get("X-Query-Tags")) + }, false) + + _, err := api.DataQuery(context.Background(), lokiQuery{Expr: "", SupportingQueryType: dataquery.SupportingQueryTypeInfiniteScroll, QueryType: QueryTypeRange}, ResponseOpts{}) + require.NoError(t, err) + require.True(t, called) + }) +} + func TestApiUrlHandling(t *testing.T) { response := []byte(` { diff --git a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go index c79be5cae6f..db33add2813 100644 --- a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go @@ -30,9 +30,10 @@ const ( // Defines values for SupportingQueryType. const ( - SupportingQueryTypeDataSample SupportingQueryType = "dataSample" - SupportingQueryTypeLogsSample SupportingQueryType = "logsSample" - SupportingQueryTypeLogsVolume SupportingQueryType = "logsVolume" + SupportingQueryTypeDataSample SupportingQueryType = "dataSample" + SupportingQueryTypeInfiniteScroll SupportingQueryType = "infiniteScroll" + SupportingQueryTypeLogsSample SupportingQueryType = "logsSample" + SupportingQueryTypeLogsVolume SupportingQueryType = "logsVolume" ) // These are the common properties available to all queries in all datasources. diff --git a/pkg/tsdb/loki/types.go b/pkg/tsdb/loki/types.go index 06f8b1836b5..5ac5ee514c3 100644 --- a/pkg/tsdb/loki/types.go +++ b/pkg/tsdb/loki/types.go @@ -16,10 +16,11 @@ const ( ) const ( - SupportingQueryLogsVolume = dataquery.SupportingQueryTypeLogsVolume - SupportingQueryLogsSample = dataquery.SupportingQueryTypeLogsSample - SupportingQueryDataSample = dataquery.SupportingQueryTypeDataSample - SupportingQueryNone SupportingQueryType = "none" + SupportingQueryLogsVolume = dataquery.SupportingQueryTypeLogsVolume + SupportingQueryLogsSample = dataquery.SupportingQueryTypeLogsSample + SupportingQueryDataSample = dataquery.SupportingQueryTypeDataSample + SupportingQueryInfiniteScroll = dataquery.SupportingQueryTypeInfiniteScroll + SupportingQueryNone SupportingQueryType = "none" ) const ( diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 47f58b0ebbc..b2b123fa22b 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -41,6 +41,7 @@ import { getShiftedTimeRange } from 'app/core/utils/timePicker'; import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils'; import { infiniteScrollRefId } from 'app/features/logs/logsModel'; import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; +import { SupportingQueryType } from 'app/plugins/datasource/loki/types'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { createAsyncThunk, @@ -732,6 +733,7 @@ export const runLoadMoreLogsQueries = createAsyncThunk Date: Mon, 29 Jan 2024 10:26:16 +0000 Subject: [PATCH 067/169] Update dependency browserslist to v4.22.3 --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 84b3455783c..ea02e6c226c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11697,16 +11697,16 @@ __metadata: linkType: hard "browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2": - version: 4.22.2 - resolution: "browserslist@npm:4.22.2" + version: 4.22.3 + resolution: "browserslist@npm:4.22.3" dependencies: - caniuse-lite: "npm:^1.0.30001565" - electron-to-chromium: "npm:^1.4.601" + caniuse-lite: "npm:^1.0.30001580" + electron-to-chromium: "npm:^1.4.648" node-releases: "npm:^2.0.14" update-browserslist-db: "npm:^1.0.13" bin: browserslist: cli.js - checksum: e3590793db7f66ad3a50817e7b7f195ce61e029bd7187200244db664bfbe0ac832f784e4f6b9c958aef8ea4abe001ae7880b7522682df521f4bc0a5b67660b5e + checksum: d46a906c79dfe95d9702c020afbe5b7b4dbe2019b85432e7a020326adff27e63e3c0a52dc8d4e73247060bbe2c13f000714741903cf96a16baae9c216dc74c75 languageName: node linkType: hard @@ -12013,10 +12013,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001565, caniuse-lite@npm:^1.0.30001578": - version: 1.0.30001579 - resolution: "caniuse-lite@npm:1.0.30001579" - checksum: 2cd0c02e5d66b09888743ad2b624dbde697ace5c76b55bfd6065ea033f6abea8ac3f5d3c9299c042f91b396e2141b49bc61f5e17086dc9ba3a866cc6790134c0 +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001578, caniuse-lite@npm:^1.0.30001580": + version: 1.0.30001581 + resolution: "caniuse-lite@npm:1.0.30001581" + checksum: c2d049514e6af5e9a9b23646b7828191f4c2d3ef1ad999d3efe02683d56d0067d616e2eadb055fe5477f870b22e7252dc09834f95007c95f310d8eca30cfa912 languageName: node linkType: hard @@ -14701,10 +14701,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.601": - version: 1.4.625 - resolution: "electron-to-chromium@npm:1.4.625" - checksum: 610a4eaabf6a064d8f6d4dfa25c55a3940f09a3b25edc8a271821d1b270bb28c4c9f19225d81bfc59deaa12c1f8f0144f3b4510631c6b6b47e0b6216737e216a +"electron-to-chromium@npm:^1.4.648": + version: 1.4.648 + resolution: "electron-to-chromium@npm:1.4.648" + checksum: a18f06bafce9017ac7b587f76dac77063a0beb7dfcdf9d5971f72b322f56af6315e4fc3c59154a260a9188c168ac7632538797d57a8c53ab57025ace0c9441f2 languageName: node linkType: hard From ca5c297bfacdad622ec7d3017f5cec0786266666 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 29 Jan 2024 11:46:40 +0100 Subject: [PATCH 068/169] Loki/Elastic: Assert queryfix value to always be string (#81349) Fix `value` can be `string` or `number` --- public/app/features/explore/Explore.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index a9aacd41489..5132e9d1cd9 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -205,13 +205,13 @@ export class Explore extends React.PureComponent { * TODO: In the future, we would like to return active filters based the query that produced the log line. * @alpha */ - isFilterLabelActive = async (key: string, value: string, refId?: string) => { + isFilterLabelActive = async (key: string, value: string | number, refId?: string) => { const query = this.props.queries.find((q) => q.refId === refId); if (!query) { return false; } const ds = await getDataSourceSrv().get(query.datasource); - if (hasToggleableQueryFiltersSupport(ds) && ds.queryHasFilter(query, { key, value })) { + if (hasToggleableQueryFiltersSupport(ds) && ds.queryHasFilter(query, { key, value: value.toString() })) { return true; } return false; @@ -220,11 +220,11 @@ export class Explore extends React.PureComponent { /** * Used by Logs details. */ - onClickFilterLabel = (key: string, value: string, frame?: DataFrame) => { + onClickFilterLabel = (key: string, value: string | number, frame?: DataFrame) => { this.onModifyQueries( { type: 'ADD_FILTER', - options: { key, value }, + options: { key, value: value.toString() }, frame, }, frame?.refId @@ -234,11 +234,11 @@ export class Explore extends React.PureComponent { /** * Used by Logs details. */ - onClickFilterOutLabel = (key: string, value: string, frame?: DataFrame) => { + onClickFilterOutLabel = (key: string, value: string | number, frame?: DataFrame) => { this.onModifyQueries( { type: 'ADD_FILTER_OUT', - options: { key, value }, + options: { key, value: value.toString() }, frame, }, frame?.refId @@ -248,15 +248,15 @@ export class Explore extends React.PureComponent { /** * Used by Logs Popover Menu. */ - onClickFilterValue = (value: string, refId?: string) => { - this.onModifyQueries({ type: 'ADD_STRING_FILTER', options: { value } }, refId); + onClickFilterValue = (value: string | number, refId?: string) => { + this.onModifyQueries({ type: 'ADD_STRING_FILTER', options: { value: value.toString() } }, refId); }; /** * Used by Logs Popover Menu. */ - onClickFilterOutValue = (value: string, refId?: string) => { - this.onModifyQueries({ type: 'ADD_STRING_FILTER_OUT', options: { value } }, refId); + onClickFilterOutValue = (value: string | number, refId?: string) => { + this.onModifyQueries({ type: 'ADD_STRING_FILTER_OUT', options: { value: value.toString() } }, refId); }; onClickAddQueryRowButton = () => { From a3fda08d4ee45595f123d82500235cd5d4a00f05 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Mon, 29 Jan 2024 11:47:28 +0100 Subject: [PATCH 069/169] Datasources: Add concurrency number to the settings (#81212) add concurrency to the settings --- conf/defaults.ini | 5 +++++ pkg/setting/setting.go | 3 +++ 2 files changed, 8 insertions(+) diff --git a/conf/defaults.ini b/conf/defaults.ini index f037ec0de10..d26ea453e3f 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -429,6 +429,11 @@ default_home_dashboard_path = # Upper limit of data sources that Grafana will return. This limit is a temporary configuration and it will be deprecated when pagination will be introduced on the list data sources API. datasource_limit = 5000 +# Number of queries to be executed concurrently. Only for the datasource supports concurrency. +# For now only Loki and InfluxDB (with influxql) are supporting concurrency behind the feature flags. +# Check datasource documentations for enabling concurrency. +concurrent_query_count = 10 + ################################### SQL Data Sources ##################### [sql_datasources] diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 10d3b54289a..ba5fc64f7a8 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -345,6 +345,8 @@ type Cfg struct { // Data sources DataSourceLimit int + // Number of queries to be executed concurrently. Only for the datasource supports concurrency. + ConcurrentQueryCount int // SQL Data sources SqlDatasourceMaxOpenConnsDefault int @@ -1933,6 +1935,7 @@ func (cfg *Cfg) GetContentDeliveryURL(prefix string) (string, error) { func (cfg *Cfg) readDataSourcesSettings() { datasources := cfg.Raw.Section("datasources") cfg.DataSourceLimit = datasources.Key("datasource_limit").MustInt(5000) + cfg.ConcurrentQueryCount = datasources.Key("concurrent_query_count").MustInt(10) } func (cfg *Cfg) readSqlDataSourceSettings() { From 7e96a2be56469b764bccc7d34308994990993eef Mon Sep 17 00:00:00 2001 From: Misi Date: Mon, 29 Jan 2024 12:02:04 +0100 Subject: [PATCH 070/169] Auth: Reload OAuth provider after deletion of the current settings (#81374) * Reload after deletion of the current settings * Add grafana_ssosettings_setting_reload_failure_total counter * Returns successfully if data reload failed --- pkg/login/social/socialimpl/service_test.go | 2 +- .../ssosettings/ssosettingsimpl/metrics.go | 31 +++++++++++ .../ssosettings/ssosettingsimpl/service.go | 45 ++++++++++++---- .../ssosettingsimpl/service_test.go | 52 +++++++++++++++++-- 4 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 pkg/services/ssosettings/ssosettingsimpl/metrics.go diff --git a/pkg/login/social/socialimpl/service_test.go b/pkg/login/social/socialimpl/service_test.go index 9beeae7abd3..9dc51e9448e 100644 --- a/pkg/login/social/socialimpl/service_test.go +++ b/pkg/login/social/socialimpl/service_test.go @@ -73,7 +73,7 @@ func TestSocialService_ProvideService(t *testing.T) { accessControl := acimpl.ProvideAccessControl(cfg) sqlStore := db.InitTestDB(t) - ssoSettingsSvc := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routing.NewRouteRegister(), featuremgmt.WithFeatures(), secrets, &usagestats.UsageStatsMock{}) + ssoSettingsSvc := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routing.NewRouteRegister(), featuremgmt.WithFeatures(), secrets, &usagestats.UsageStatsMock{}, nil) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/services/ssosettings/ssosettingsimpl/metrics.go b/pkg/services/ssosettings/ssosettingsimpl/metrics.go new file mode 100644 index 00000000000..229abf9d431 --- /dev/null +++ b/pkg/services/ssosettings/ssosettingsimpl/metrics.go @@ -0,0 +1,31 @@ +package ssosettingsimpl + +import "github.com/prometheus/client_golang/prometheus" + +const ( + metricsNamespace = "grafana" + metricsSubSystem = "ssosettings" +) + +type metrics struct { + reloadFailures *prometheus.CounterVec +} + +func newMetrics(reg prometheus.Registerer) *metrics { + m := &metrics{ + reloadFailures: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: metricsNamespace, + Subsystem: metricsSubSystem, + Name: "setting_reload_failures_total", + Help: "Number of SSO Setting reload failures.", + }, []string{"provider"}), + } + + if reg != nil { + reg.MustRegister( + m.reloadFailures, + ) + } + + return m +} diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index 894725efef9..16047c3ab67 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -21,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/strategies" "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/client_golang/prometheus" ) var _ ssosettings.Service = (*Service)(nil) @@ -31,6 +32,7 @@ type Service struct { store ssosettings.Store ac ac.AccessControl secrets secrets.Service + metrics *metrics fbStrategies []ssosettings.FallbackStrategy reloadables map[string]ssosettings.Reloadable @@ -38,7 +40,7 @@ type Service struct { func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, routeRegister routing.RouteRegister, features featuremgmt.FeatureToggles, - secrets secrets.Service, usageStats usagestats.Service) *Service { + secrets secrets.Service, usageStats usagestats.Service, registerer prometheus.Registerer) *Service { strategies := []ssosettings.FallbackStrategy{ strategies.NewOAuthStrategy(cfg), // register other strategies here, for example SAML @@ -53,6 +55,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, ac: ac, fbStrategies: strategies, secrets: secrets, + metrics: newMetrics(registerer), reloadables: make(map[string]ssosettings.Reloadable), } @@ -193,13 +196,9 @@ func (s *Service) Upsert(ctx context.Context, settings *models.SSOSettings) erro return err } - go func() { - settings.Settings = overrideMaps(storedSettings.Settings, settings.Settings, secrets) - err = social.Reload(context.Background(), *settings) - if err != nil { - s.logger.Error("failed to reload the provider", "provider", settings.Provider, "error", err) - } - }() + settings.Settings = overrideMaps(storedSettings.Settings, settings.Settings, secrets) + + go s.reload(social, settings.Provider, *settings) return nil } @@ -212,7 +211,34 @@ func (s *Service) Delete(ctx context.Context, provider string) error { if !s.isProviderConfigurable(provider) { return ssosettings.ErrNotConfigurable } - return s.store.Delete(ctx, provider) + + social, ok := s.reloadables[provider] + if !ok { + return ssosettings.ErrInvalidProvider.Errorf("provider %s not found in reloadables", provider) + } + + err := s.store.Delete(ctx, provider) + if err != nil { + return err + } + + currentSettings, err := s.GetForProvider(ctx, provider) + if err != nil { + s.logger.Error("failed to get current settings, skipping reload", "provider", provider, "error", err) + return nil + } + + go s.reload(social, provider, *currentSettings) + + return nil +} + +func (s *Service) reload(reloadable ssosettings.Reloadable, provider string, currentSettings models.SSOSettings) { + err := reloadable.Reload(context.Background(), currentSettings) + if err != nil { + s.metrics.reloadFailures.WithLabelValues(provider).Inc() + s.logger.Error("failed to reload the provider", "provider", provider, "error", err) + } } func (s *Service) Reload(ctx context.Context, provider string) { @@ -327,6 +353,7 @@ func (s *Service) doReload(ctx context.Context) { err = connector.Reload(ctx, *setting) if err != nil { + s.metrics.reloadFailures.WithLabelValues(provider).Inc() s.logger.Error("failed to reload SSO Settings", "provider", provider, "err", err) continue } diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 7537dbe369c..b29e2a509d8 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -1007,17 +1008,45 @@ func TestService_Delete(t *testing.T) { t.Run("successfully delete SSO settings", func(t *testing.T) { env := setupTestEnv(t) + var wg sync.WaitGroup + wg.Add(1) + provider := social.AzureADProviderName - env.store.ExpectedError = nil + reloadable := ssosettingstests.NewMockReloadable(t) + env.reloadables[provider] = reloadable + + env.fallbackStrategy.ExpectedConfigs = map[string]map[string]any{ + provider: { + "client_id": "client-id", + "client_secret": "client-secret", + "enabled": true, + }, + } + + reloadable.On("Reload", mock.Anything, mock.MatchedBy(func(settings models.SSOSettings) bool { + wg.Done() + return settings.Provider == provider && + settings.ID == "" && + maps.Equal(settings.Settings, map[string]any{ + "client_id": "client-id", + "client_secret": "client-secret", + "enabled": true, + }) + })).Return(nil).Once() err := env.service.Delete(context.Background(), provider) require.NoError(t, err) + + // wait for the goroutine first to assert the Reload call + wg.Wait() }) - t.Run("SSO settings not found for the specified provider", func(t *testing.T) { + t.Run("return error if SSO setting was not found for the specified provider", func(t *testing.T) { env := setupTestEnv(t) provider := social.AzureADProviderName + reloadable := ssosettingstests.NewMockReloadable(t) + env.reloadables[provider] = reloadable env.store.ExpectedError = ssosettings.ErrNotFound err := env.service.Delete(context.Background(), provider) @@ -1036,7 +1065,7 @@ func TestService_Delete(t *testing.T) { require.ErrorIs(t, err, ssosettings.ErrNotConfigurable) }) - t.Run("store fails to delete the SSO settings for the specified provider", func(t *testing.T) { + t.Run("return error when store fails to delete the SSO settings for the specified provider", func(t *testing.T) { env := setupTestEnv(t) provider := social.AzureADProviderName @@ -1046,6 +1075,22 @@ func TestService_Delete(t *testing.T) { require.Error(t, err) require.NotErrorIs(t, err, ssosettings.ErrNotFound) }) + + t.Run("return successfully when the deletion was successful but reloading the settings fail", func(t *testing.T) { + env := setupTestEnv(t) + + provider := social.AzureADProviderName + reloadable := ssosettingstests.NewMockReloadable(t) + env.reloadables[provider] = reloadable + + env.store.GetFn = func(ctx context.Context, provider string) (*models.SSOSettings, error) { + return nil, errors.New("failed to get sso settings") + } + + err := env.service.Delete(context.Background(), provider) + + require.NoError(t, err) + }) } func TestService_DoReload(t *testing.T) { @@ -1223,6 +1268,7 @@ func setupTestEnv(t *testing.T) testEnv { ac: accessControl, fbStrategies: []ssosettings.FallbackStrategy{fallbackStrategy}, reloadables: reloadables, + metrics: newMetrics(prometheus.NewRegistry()), secrets: secrets, } From bcc240956464e392b7a2d156eca6a50defb629bd Mon Sep 17 00:00:00 2001 From: Misi Date: Mon, 29 Jan 2024 12:04:22 +0100 Subject: [PATCH 071/169] Auth: Add validation to Generic OAuth API and UI (#81345) * wip * Update validation * Chore: Remove InputControl usage * Fixes, validation * Remove empty option * Validation changes * Add tests, rename * lint --------- Co-authored-by: Clarity-89 --- pkg/login/social/connectors/azuread_oauth.go | 8 +- .../social/connectors/azuread_oauth_test.go | 46 +- pkg/login/social/connectors/generic_oauth.go | 8 +- .../social/connectors/generic_oauth_test.go | 33 +- pkg/login/social/connectors/github_oauth.go | 3 +- .../social/connectors/github_oauth_test.go | 43 +- .../social/connectors/gitlab_oauth_test.go | 33 +- .../social/connectors/google_oauth_test.go | 33 +- .../social/connectors/okta_oauth_test.go | 33 +- pkg/login/social/connectors/social_base.go | 6 +- pkg/services/ssosettings/errors.go | 8 + .../features/auth-config/FieldRenderer.tsx | 22 +- .../auth-config/ProviderConfigForm.test.tsx | 22 +- .../auth-config/ProviderConfigForm.tsx | 20 +- public/app/features/auth-config/fields.tsx | 565 ++++++++++-------- public/app/features/auth-config/utils/data.ts | 14 +- 16 files changed, 529 insertions(+), 368 deletions(-) diff --git a/pkg/login/social/connectors/azuread_oauth.go b/pkg/login/social/connectors/azuread_oauth.go index 98f21607146..712d0f145ba 100644 --- a/pkg/login/social/connectors/azuread_oauth.go +++ b/pkg/login/social/connectors/azuread_oauth.go @@ -12,6 +12,7 @@ import ( jose "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/jwt" + "github.com/google/uuid" "golang.org/x/oauth2" "github.com/grafana/grafana/pkg/infra/remotecache" @@ -195,7 +196,12 @@ func (s *SocialAzureAD) Validate(ctx context.Context, settings ssoModels.SSOSett return err } - // add specific validation rules for AzureAD + for _, groupId := range info.AllowedGroups { + _, err := uuid.Parse(groupId) + if err != nil { + return ssosettings.ErrInvalidOAuthConfig("One or more of the Allowed groups are not in the correct format. Allowed groups should be a list of Object Ids.") + } + } return nil } diff --git a/pkg/login/social/connectors/azuread_oauth_test.go b/pkg/login/social/connectors/azuread_oauth_test.go index aed48e98d9b..e59803135b2 100644 --- a/pkg/login/social/connectors/azuread_oauth_test.go +++ b/pkg/login/social/connectors/azuread_oauth_test.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/ssosettings" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/setting" @@ -991,18 +992,18 @@ func TestSocialAzureAD_InitializeExtraFields(t *testing.T) { func TestSocialAzureAD_Validate(t *testing.T) { testCases := []struct { - name string - settings ssoModels.SSOSettings - expectError bool + name string + settings ssoModels.SSOSettings + wantErr error }{ { name: "SSOSettings is valid", settings: ssoModels.SSOSettings{ Settings: map[string]any{ - "client_id": "client-id", + "client_id": "client-id", + "allowed_groups": "0bb9c9cc-4945-418f-9b6a-c1d3b81141b0, 6034d328-0e6a-4240-8d03-cb9f2c1f16e4", }, }, - expectError: false, }, { name: "fails if settings map contains an invalid field", @@ -1012,7 +1013,7 @@ func TestSocialAzureAD_Validate(t *testing.T) { "invalid_field": []int{1, 2, 3}, }, }, - expectError: true, + wantErr: ssosettings.ErrInvalidSettings, }, { name: "fails if client id is empty", @@ -1021,14 +1022,35 @@ func TestSocialAzureAD_Validate(t *testing.T) { "client_id": "", }, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, { name: "fails if client id does not exist", settings: ssoModels.SSOSettings{ Settings: map[string]any{}, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if allowed groups are not uuids", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allowed_groups": "abc, def", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if both allow assign grafana admin and skip org role sync are enabled", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allow_assign_grafana_admin": "true", + "skip_org_role_sync": "true", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, } @@ -1037,11 +1059,11 @@ func TestSocialAzureAD_Validate(t *testing.T) { s := NewAzureADProvider(&social.OAuthInfo{}, &setting.Cfg{}, &ssosettingstests.MockService{}, featuremgmt.WithFeatures(), nil) err := s.Validate(context.Background(), tc.settings) - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + return } + require.NoError(t, err) }) } } diff --git a/pkg/login/social/connectors/generic_oauth.go b/pkg/login/social/connectors/generic_oauth.go index 87dce610fc7..3e1c3cecb38 100644 --- a/pkg/login/social/connectors/generic_oauth.go +++ b/pkg/login/social/connectors/generic_oauth.go @@ -78,7 +78,13 @@ func (s *SocialGenericOAuth) Validate(ctx context.Context, settings ssoModels.SS return err } - // add specific validation rules for Generic OAuth + if info.Extra[teamIdsKey] != "" && (info.TeamIdsAttributePath == "" || info.TeamsUrl == "") { + return ssosettings.ErrInvalidOAuthConfig("If Team Ids are configured then Team Ids attribute path and Teams URL must be configured.") + } + + if info.AllowedGroups != nil && len(info.AllowedGroups) > 0 && info.GroupsAttributePath == "" { + return ssosettings.ErrInvalidOAuthConfig("If Allowed groups are configured then Groups attribute path must be configured.") + } return nil } diff --git a/pkg/login/social/connectors/generic_oauth_test.go b/pkg/login/social/connectors/generic_oauth_test.go index bf6dc50c366..edcc5eb76e3 100644 --- a/pkg/login/social/connectors/generic_oauth_test.go +++ b/pkg/login/social/connectors/generic_oauth_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/ssosettings" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/setting" @@ -919,9 +920,9 @@ func TestSocialGenericOAuth_InitializeExtraFields(t *testing.T) { func TestSocialGenericOAuth_Validate(t *testing.T) { testCases := []struct { - name string - settings ssoModels.SSOSettings - expectError bool + name string + settings ssoModels.SSOSettings + wantErr error }{ { name: "SSOSettings is valid", @@ -930,7 +931,6 @@ func TestSocialGenericOAuth_Validate(t *testing.T) { "client_id": "client-id", }, }, - expectError: false, }, { name: "fails if settings map contains an invalid field", @@ -940,7 +940,7 @@ func TestSocialGenericOAuth_Validate(t *testing.T) { "invalid_field": []int{1, 2, 3}, }, }, - expectError: true, + wantErr: ssosettings.ErrInvalidSettings, }, { name: "fails if client id is empty", @@ -949,14 +949,25 @@ func TestSocialGenericOAuth_Validate(t *testing.T) { "client_id": "", }, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, { name: "fails if client id does not exist", settings: ssoModels.SSOSettings{ Settings: map[string]any{}, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if both allow assign grafana admin and skip org role sync are enabled", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allow_assign_grafana_admin": "true", + "skip_org_role_sync": "true", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, } @@ -965,11 +976,11 @@ func TestSocialGenericOAuth_Validate(t *testing.T) { s := NewGenericOAuthProvider(&social.OAuthInfo{}, &setting.Cfg{}, &ssosettingstests.MockService{}, featuremgmt.WithFeatures()) err := s.Validate(context.Background(), tc.settings) - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + return } + require.NoError(t, err) }) } } diff --git a/pkg/login/social/connectors/github_oauth.go b/pkg/login/social/connectors/github_oauth.go index 4a89bac5eed..de7fc6def39 100644 --- a/pkg/login/social/connectors/github_oauth.go +++ b/pkg/login/social/connectors/github_oauth.go @@ -89,8 +89,7 @@ func (s *SocialGithub) Validate(ctx context.Context, settings ssoModels.SSOSetti teamIds := mustInts(teamIdsSplitted) if len(teamIdsSplitted) != len(teamIds) { - s.log.Warn("Failed to parse team ids. Team ids must be a list of numbers.", "teamIds", teamIdsSplitted) - return ssosettings.ErrInvalidSettings.Errorf("Failed to parse team ids. Team ids must be a list of numbers.") + return ssosettings.ErrInvalidOAuthConfig("Failed to parse Team Ids. Team Ids must be a list of numbers.") } return nil diff --git a/pkg/login/social/connectors/github_oauth_test.go b/pkg/login/social/connectors/github_oauth_test.go index 974969518b9..f0e09a85e07 100644 --- a/pkg/login/social/connectors/github_oauth_test.go +++ b/pkg/login/social/connectors/github_oauth_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/ssosettings" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/setting" @@ -345,9 +346,9 @@ func TestSocialGitHub_InitializeExtraFields(t *testing.T) { func TestSocialGitHub_Validate(t *testing.T) { testCases := []struct { - name string - settings ssoModels.SSOSettings - expectError bool + name string + settings ssoModels.SSOSettings + wantErr error }{ { name: "SSOSettings is valid", @@ -356,7 +357,6 @@ func TestSocialGitHub_Validate(t *testing.T) { "client_id": "client-id", }, }, - expectError: false, }, { name: "fails if settings map contains an invalid field", @@ -366,7 +366,7 @@ func TestSocialGitHub_Validate(t *testing.T) { "invalid_field": []int{1, 2, 3}, }, }, - expectError: true, + wantErr: ssosettings.ErrInvalidSettings, }, { name: "fails if client id is empty", @@ -375,14 +375,35 @@ func TestSocialGitHub_Validate(t *testing.T) { "client_id": "", }, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, { name: "fails if client id does not exist", settings: ssoModels.SSOSettings{ Settings: map[string]any{}, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if team ids are not integers", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "team_ids": "abc1234,5678,def", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if both allow assign grafana admin and skip org role sync are enabled", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allow_assign_grafana_admin": "true", + "skip_org_role_sync": "true", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, } @@ -391,11 +412,11 @@ func TestSocialGitHub_Validate(t *testing.T) { s := NewGitHubProvider(&social.OAuthInfo{}, &setting.Cfg{}, &ssosettingstests.MockService{}, featuremgmt.WithFeatures()) err := s.Validate(context.Background(), tc.settings) - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + return } + require.NoError(t, err) }) } } diff --git a/pkg/login/social/connectors/gitlab_oauth_test.go b/pkg/login/social/connectors/gitlab_oauth_test.go index 00a86153159..f2bf015da03 100644 --- a/pkg/login/social/connectors/gitlab_oauth_test.go +++ b/pkg/login/social/connectors/gitlab_oauth_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/ssosettings" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/setting" @@ -463,9 +464,9 @@ func TestSocialGitlab_GetGroupsNextPage(t *testing.T) { func TestSocialGitlab_Validate(t *testing.T) { testCases := []struct { - name string - settings ssoModels.SSOSettings - expectError bool + name string + settings ssoModels.SSOSettings + wantErr error }{ { name: "SSOSettings is valid", @@ -474,7 +475,6 @@ func TestSocialGitlab_Validate(t *testing.T) { "client_id": "client-id", }, }, - expectError: false, }, { name: "fails if settings map contains an invalid field", @@ -484,7 +484,7 @@ func TestSocialGitlab_Validate(t *testing.T) { "invalid_field": []int{1, 2, 3}, }, }, - expectError: true, + wantErr: ssosettings.ErrInvalidSettings, }, { name: "fails if client id is empty", @@ -493,14 +493,25 @@ func TestSocialGitlab_Validate(t *testing.T) { "client_id": "", }, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, { name: "fails if client id does not exist", settings: ssoModels.SSOSettings{ Settings: map[string]any{}, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if both allow assign grafana admin and skip org role sync are enabled", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allow_assign_grafana_admin": "true", + "skip_org_role_sync": "true", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, } @@ -509,11 +520,11 @@ func TestSocialGitlab_Validate(t *testing.T) { s := NewGitLabProvider(&social.OAuthInfo{}, &setting.Cfg{}, &ssosettingstests.MockService{}, featuremgmt.WithFeatures()) err := s.Validate(context.Background(), tc.settings) - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + return } + require.NoError(t, err) }) } } diff --git a/pkg/login/social/connectors/google_oauth_test.go b/pkg/login/social/connectors/google_oauth_test.go index 79a4cb9d8cf..df66caa0d7c 100644 --- a/pkg/login/social/connectors/google_oauth_test.go +++ b/pkg/login/social/connectors/google_oauth_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/ssosettings" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/setting" @@ -668,9 +669,9 @@ func TestSocialGoogle_UserInfo(t *testing.T) { func TestSocialGoogle_Validate(t *testing.T) { testCases := []struct { - name string - settings ssoModels.SSOSettings - expectError bool + name string + settings ssoModels.SSOSettings + wantErr error }{ { name: "SSOSettings is valid", @@ -679,7 +680,6 @@ func TestSocialGoogle_Validate(t *testing.T) { "client_id": "client-id", }, }, - expectError: false, }, { name: "fails if settings map contains an invalid field", @@ -689,7 +689,7 @@ func TestSocialGoogle_Validate(t *testing.T) { "invalid_field": []int{1, 2, 3}, }, }, - expectError: true, + wantErr: ssosettings.ErrInvalidSettings, }, { name: "fails if client id is empty", @@ -698,14 +698,25 @@ func TestSocialGoogle_Validate(t *testing.T) { "client_id": "", }, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, { name: "fails if client id does not exist", settings: ssoModels.SSOSettings{ Settings: map[string]any{}, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if both allow assign grafana admin and skip org role sync are enabled", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allow_assign_grafana_admin": "true", + "skip_org_role_sync": "true", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, } @@ -714,11 +725,11 @@ func TestSocialGoogle_Validate(t *testing.T) { s := NewGoogleProvider(&social.OAuthInfo{}, &setting.Cfg{}, &ssosettingstests.MockService{}, featuremgmt.WithFeatures()) err := s.Validate(context.Background(), tc.settings) - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + return } + require.NoError(t, err) }) } } diff --git a/pkg/login/social/connectors/okta_oauth_test.go b/pkg/login/social/connectors/okta_oauth_test.go index 6e332a0a1df..2bddad48984 100644 --- a/pkg/login/social/connectors/okta_oauth_test.go +++ b/pkg/login/social/connectors/okta_oauth_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/ssosettings" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/setting" @@ -136,9 +137,9 @@ func TestSocialOkta_UserInfo(t *testing.T) { func TestSocialOkta_Validate(t *testing.T) { testCases := []struct { - name string - settings ssoModels.SSOSettings - expectError bool + name string + settings ssoModels.SSOSettings + wantErr error }{ { name: "SSOSettings is valid", @@ -147,7 +148,6 @@ func TestSocialOkta_Validate(t *testing.T) { "client_id": "client-id", }, }, - expectError: false, }, { name: "fails if settings map contains an invalid field", @@ -157,7 +157,7 @@ func TestSocialOkta_Validate(t *testing.T) { "invalid_field": []int{1, 2, 3}, }, }, - expectError: true, + wantErr: ssosettings.ErrInvalidSettings, }, { name: "fails if client id is empty", @@ -166,14 +166,25 @@ func TestSocialOkta_Validate(t *testing.T) { "client_id": "", }, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, { name: "fails if client id does not exist", settings: ssoModels.SSOSettings{ Settings: map[string]any{}, }, - expectError: true, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "fails if both allow assign grafana admin and skip org role sync are enabled", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "allow_assign_grafana_admin": "true", + "skip_org_role_sync": "true", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, } @@ -182,11 +193,11 @@ func TestSocialOkta_Validate(t *testing.T) { s := NewOktaProvider(&social.OAuthInfo{}, &setting.Cfg{}, &ssosettingstests.MockService{}, featuremgmt.WithFeatures()) err := s.Validate(context.Background(), tc.settings) - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + return } + require.NoError(t, err) }) } } diff --git a/pkg/login/social/connectors/social_base.go b/pkg/login/social/connectors/social_base.go index 10f990f5600..31f8163cbba 100644 --- a/pkg/login/social/connectors/social_base.go +++ b/pkg/login/social/connectors/social_base.go @@ -222,7 +222,11 @@ func getRoleFromSearch(role string) (org.RoleType, bool) { func validateInfo(info *social.OAuthInfo) error { if info.ClientId == "" { - return ssosettings.ErrEmptyClientId.Errorf("clientId is empty") + return ssosettings.ErrInvalidOAuthConfig("ClientId is empty") + } + + if info.AllowAssignGrafanaAdmin && info.SkipOrgRoleSync { + return ssosettings.ErrInvalidOAuthConfig("Allow assign Grafana Admin and Skip org role sync are both set thus Grafana Admin role will not be synced. Consider setting one or the other.") } return nil diff --git a/pkg/services/ssosettings/errors.go b/pkg/services/ssosettings/errors.go index 6c548624625..9e1169d1a17 100644 --- a/pkg/services/ssosettings/errors.go +++ b/pkg/services/ssosettings/errors.go @@ -10,6 +10,14 @@ var ( ErrNotConfigurable = errNotFoundBase.Errorf("not configurable") + ErrBaseInvalidOAuthConfig = errutil.ValidationFailed("sso.invalidOauthConfig") + + ErrInvalidOAuthConfig = func(msg string) error { + base := ErrBaseInvalidOAuthConfig.Errorf("OAuth settings are invalid") + base.PublicMessage = msg + return base + } + ErrInvalidProvider = errutil.ValidationFailed("sso.invalidProvider", errutil.WithPublicMessage("Provider is invalid")) ErrInvalidSettings = errutil.ValidationFailed("sso.settings", errutil.WithPublicMessage("Settings field is invalid")) ErrEmptyClientId = errutil.ValidationFailed("sso.emptyClientId", errutil.WithPublicMessage("ClientId cannot be empty")) diff --git a/public/app/features/auth-config/FieldRenderer.tsx b/public/app/features/auth-config/FieldRenderer.tsx index 4402561ca1c..4c74ff5c0c8 100644 --- a/public/app/features/auth-config/FieldRenderer.tsx +++ b/public/app/features/auth-config/FieldRenderer.tsx @@ -1,8 +1,8 @@ import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; -import { UseFormReturn } from 'react-hook-form'; +import { UseFormReturn, Controller } from 'react-hook-form'; -import { Checkbox, Field, Input, InputControl, SecretInput, Select, Switch, useTheme2 } from '@grafana/ui'; +import { Checkbox, Field, Input, SecretInput, Select, Switch, useTheme2 } from '@grafana/ui'; import { fieldMap } from './fields'; import { SSOProviderDTO, SSOSettingsField } from './types'; @@ -13,6 +13,7 @@ interface FieldRendererProps field: SSOSettingsField; errors: UseFormReturn['formState']['errors']; secretConfigured: boolean; + provider: string; } export const FieldRenderer = ({ @@ -24,12 +25,13 @@ export const FieldRenderer = ({ control, unregister, secretConfigured, + provider, }: FieldRendererProps) => { const [isSecretConfigured, setIsSecretConfigured] = useState(secretConfigured); const isDependantField = typeof field !== 'string'; const name = isDependantField ? field.name : field; const parentValue = isDependantField ? watch(field.dependsOn) : null; - const fieldData = fieldMap[name]; + const fieldData = fieldMap(provider)[name]; const theme = useTheme2(); // Unregister a field that depends on a toggle to clear its data useEffect(() => { @@ -66,18 +68,13 @@ export const FieldRenderer = ({ case 'text': return ( - + ); case 'secret': return ( - - { await user.click(screen.getByRole('button', { name: /Save/i })); await waitFor(() => { - expect(putMock).toHaveBeenCalledWith('/api/v1/sso-settings/github', { - ...testConfig, - settings: { - allowedOrganizations: 'test-org1,test-org2', - clientId: 'test-client-id', - clientSecret: 'test-client-secret', - teamIds: '12324', - enabled: true, + expect(putMock).toHaveBeenCalledWith( + '/api/v1/sso-settings/github', + { + ...testConfig, + settings: { + allowedOrganizations: 'test-org1,test-org2', + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + teamIds: '12324', + enabled: true, + }, }, - }); + { showErrorAlert: false } + ); }); }); diff --git a/public/app/features/auth-config/ProviderConfigForm.tsx b/public/app/features/auth-config/ProviderConfigForm.tsx index 97e235468c2..1fcf5d5c733 100644 --- a/public/app/features/auth-config/ProviderConfigForm.tsx +++ b/public/app/features/auth-config/ProviderConfigForm.tsx @@ -31,7 +31,7 @@ export const ProviderConfigForm = ({ config, provider, isLoading }: ProviderConf setValue, unregister, formState: { errors, dirtyFields, isSubmitted }, - } = useForm({ defaultValues: dataToDTO(config), mode: 'onChange', reValidateMode: 'onChange' }); + } = useForm({ defaultValues: dataToDTO(config), mode: 'onSubmit', reValidateMode: 'onChange' }); const [isSaving, setIsSaving] = useState(false); const providerFields = fields[provider]; const [submitError, setSubmitError] = useState(false); @@ -44,11 +44,17 @@ export const ProviderConfigForm = ({ config, provider, isLoading }: ProviderConf setSubmitError(false); const requestData = dtoToData(data, provider); try { - await getBackendSrv().put(`/api/v1/sso-settings/${provider}`, { - id: config?.id, - provider: config?.provider, - settings: { ...requestData }, - }); + await getBackendSrv().put( + `/api/v1/sso-settings/${provider}`, + { + id: config?.id, + provider: config?.provider, + settings: { ...requestData }, + }, + { + showErrorAlert: false, + } + ); appEvents.publish({ type: AppEvents.alertSuccess.name, @@ -133,6 +139,7 @@ export const ProviderConfigForm = ({ config, provider, isLoading }: ProviderConf register={register} watch={watch} unregister={unregister} + provider={provider} secretConfigured={!!config?.settings.clientSecret} /> ); @@ -154,6 +161,7 @@ export const ProviderConfigForm = ({ config, provider, isLoading }: ProviderConf register={register} watch={watch} unregister={unregister} + provider={provider} secretConfigured={!!config?.settings.clientSecret} /> ); diff --git a/public/app/features/auth-config/fields.tsx b/public/app/features/auth-config/fields.tsx index 974c6eae13b..c2362ca8623 100644 --- a/public/app/features/auth-config/fields.tsx +++ b/public/app/features/auth-config/fields.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { validate as uuidValidate } from 'uuid'; import { TextLink } from '@grafana/ui'; @@ -95,276 +96,314 @@ export const sectionFields: Section = { /** * List all the fields that can be used in the form */ -export const fieldMap: Record = { - clientId: { - label: 'Client Id', - type: 'text', - description: 'The client ID of your OAuth2 app.', - validation: { - required: true, - message: 'This field is required', - }, - }, - clientSecret: { - label: 'Client Secret', - type: 'secret', - description: 'The client secret of your OAuth2 app.', - }, - teamIds: { - label: 'Team Ids', - type: 'select', - description: - 'String list of team IDs. If set, the user must be a member of one of the given teams to log in. \n' + - 'If you configure team_ids, you must also configure teams_url and team_ids_attribute_path.', - multi: true, - allowCustomValue: true, - options: [], - placeholder: 'Enter team IDs and press Enter to add', - validation: { - validate: (value) => { - if (typeof value === 'string') { - return isNumeric(value); - } - if (isSelectableValue(value)) { - return value.every((v) => v?.value && isNumeric(v.value)); - } - return true; +export function fieldMap(provider: string): Record { + return { + clientId: { + label: 'Client Id', + type: 'text', + description: 'The client Id of your OAuth2 app.', + validation: { + required: true, + message: 'This field is required', }, - message: 'Team ID must be a number.', }, - }, - allowedOrganizations: { - label: 'Allowed Organizations', - type: 'select', - description: - 'List of comma- or space-separated organizations. The user should be a member \n' + - 'of at least one organization to log in.', - multi: true, - allowCustomValue: true, - options: [], - placeholder: 'Enter organizations (my-team, myteam...) and press Enter to add', - }, - allowedDomains: { - label: 'Allowed Domains', - type: 'select', - description: - 'List of comma- or space-separated domains. The user should belong to at least \n' + 'one domain to log in.', - multi: true, - allowCustomValue: true, - options: [], - }, - authUrl: { - label: 'Auth Url', - type: 'text', - description: 'The authorization endpoint of your OAuth2 provider.', - validation: { - required: false, + clientSecret: { + label: 'Client secret', + type: 'secret', + description: 'The client secret of your OAuth2 app.', }, - }, - authStyle: { - label: 'Auth Style', - type: 'select', - description: 'It determines how client_id and client_secret are sent to Oauth2 provider. Default is AutoDetect.', - multi: false, - options: [ - { value: 'AutoDetect', label: 'AutoDetect' }, - { value: 'InParams', label: 'InParams' }, - { value: 'InHeader', label: 'InHeader' }, - ], - defaultValue: 'AutoDetect', - }, - tokenUrl: { - label: 'Token Url', - type: 'text', - description: 'The token endpoint of your OAuth2 provider.', - validation: { - required: false, + allowedOrganizations: { + label: 'Allowed organizations', + type: 'select', + description: + 'List of comma- or space-separated organizations. The user should be a member \n' + + 'of at least one organization to log in.', + multi: true, + allowCustomValue: true, + options: [], + placeholder: 'Enter organizations (my-team, myteam...) and press Enter to add', }, - }, - scopes: { - label: 'Scopes', - type: 'select', - description: 'List of comma- or space-separated OAuth2 scopes.', - multi: true, - allowCustomValue: true, - options: [], - }, - allowedGroups: { - label: 'Allowed Groups', - type: 'select', - description: - 'List of comma- or space-separated groups. The user should be a member of \n' + - 'at least one group to log in. If you configure allowed_groups, you must also configure \n' + - 'groups_attribute_path.', - multi: true, - allowCustomValue: true, - options: [], - }, - apiUrl: { - label: 'API Url', - type: 'text', - description: ( - <> - The user information endpoint of your OAuth2 provider. Information returned by this endpoint must be compatible - with{' '} - - OpenID UserInfo - - . - - ), - validation: { - required: false, + allowedDomains: { + label: 'Allowed domains', + type: 'select', + description: + 'List of comma- or space-separated domains. The user should belong to at least \n' + 'one domain to log in.', + multi: true, + allowCustomValue: true, + options: [], }, - }, - roleAttributePath: { - label: 'Role Attribute Path', - description: 'JMESPath expression to use for Grafana role lookup.', - type: 'text', - validation: { - required: false, + authUrl: { + label: 'Auth URL', + type: 'text', + description: 'The authorization endpoint of your OAuth2 provider.', + validation: { + required: false, + }, }, - }, - name: { - label: 'Display name', - description: 'Helpful if you use more than one identity providers or SSO protocols.', - type: 'text', - }, - allowSignUp: { - label: 'Allow sign up', - description: 'If not enabled, only existing Grafana users can log in using OAuth.', - type: 'switch', - }, - autoLogin: { - label: 'Auto login', - description: 'Log in automatically, skipping the login screen.', - type: 'switch', - }, - signoutRedirectUrl: { - label: 'Sign out redirect URL', - description: 'The URL to redirect the user to after signing out from Grafana.', - type: 'text', - validation: { - required: false, + authStyle: { + label: 'Auth style', + type: 'select', + description: 'It determines how client_id and client_secret are sent to Oauth2 provider. Default is AutoDetect.', + multi: false, + options: [ + { value: 'AutoDetect', label: 'AutoDetect' }, + { value: 'InParams', label: 'InParams' }, + { value: 'InHeader', label: 'InHeader' }, + ], + defaultValue: 'AutoDetect', }, - }, - emailAttributeName: { - label: 'Email attribute name', - description: 'Name of the key to use for user email lookup within the attributes map of OAuth2 ID token.', - type: 'text', - }, - emailAttributePath: { - label: 'Email attribute path', - description: 'JMESPath expression to use for user email lookup from the user information.', - type: 'text', - }, - nameAttributePath: { - label: 'Name attribute path', - description: - 'JMESPath expression to use for user name lookup from the user ID token. \n' + - 'This name will be used as the user’s display name.', - type: 'text', - }, - loginAttributePath: { - label: 'Login attribute path', - description: 'JMESPath expression to use for user login lookup from the user ID token.', - type: 'text', - }, - idTokenAttributeName: { - label: 'ID token attribute name', - description: 'The name of the key used to extract the ID token from the returned OAuth2 token.', - type: 'text', - }, - roleAttributeStrict: { - label: 'Role attribute strict mode', - description: 'If enabled, denies user login if the Grafana role cannot be extracted using Role attribute path.', - type: 'switch', - }, - allowAssignGrafanaAdmin: { - label: 'Allow assign Grafana admin', - description: 'If enabled, it will automatically sync the Grafana server administrator role.', - type: 'switch', - }, - skipOrgRoleSync: { - label: 'Skip organization role sync', - description: 'Prevent synchronizing users’ organization roles from your IdP.', - type: 'switch', - }, - defineAllowedGroups: { - label: 'Define Allowed Groups', - type: 'switch', - }, - defineAllowedTeamsIds: { - label: 'Define Allowed Teams Ids', - type: 'switch', - }, - usePkce: { - label: 'Use Pkce', - description: ( - <> - If enabled, Grafana will use{' '} - - Proof Key for Code Exchange (PKCE) - {' '} - with the OAuth2 Authorization Code Grant. - - ), - type: 'checkbox', - }, - useRefreshToken: { - label: 'Use Refresh Token', - description: - 'If enabled, Grafana will fetch a new access token using the refresh token provided by the OAuth2 provider.', - type: 'checkbox', - }, - configureTLS: { - label: 'Configure TLS', - type: 'switch', - }, - tlsClientCa: { - label: 'TLS Client CA', - description: 'The path to the trusted certificate authority list.', - type: 'text', - }, - tlsClientCert: { - label: 'TLS Client Cert', - description: 'The path to the certificate', - type: 'text', - }, - tlsClientKey: { - label: 'TLS Client Key', - description: 'The path to the key', - type: 'text', - }, - tlsSkipVerifyInsecure: { - label: 'TLS Skip Verify', - description: - 'If enabled, the client accepts any certificate presented by the server and any host \n' + - 'name in that certificate. You should only use this for testing, because this mode leaves \n' + - 'SSL/TLS susceptible to man-in-the-middle attacks.', - type: 'switch', - }, - groupsAttributePath: { - label: 'Groups attribute path', - description: - 'JMESPath expression to use for user group lookup. If you configure allowed_groups, \n' + - 'you must also configure groups_attribute_path.', - type: 'text', - }, - teamsUrl: { - label: 'Teams URL', - description: - 'The URL used to query for team IDs. If not set, the default value is /teams. \n' + - 'If you configure teams_url, you must also configure team_ids_attribute_path.', - type: 'text', - }, - teamIdsAttributePath: { - label: 'Team IDs attribute path', - description: - 'The JMESPath expression to use for Grafana team ID lookup within the results returned by the teams_url endpoint.', - type: 'text', - }, -}; + tokenUrl: { + label: 'Token URL', + type: 'text', + description: 'The token endpoint of your OAuth2 provider.', + validation: { + required: false, + }, + }, + scopes: { + label: 'Scopes', + type: 'select', + description: 'List of comma- or space-separated OAuth2 scopes.', + multi: true, + allowCustomValue: true, + options: [], + }, + allowedGroups: { + label: 'Allowed groups', + type: 'select', + description: + 'List of comma- or space-separated groups. The user should be a member of \n' + + 'at least one group to log in. If you configure allowed_groups, you must also configure \n' + + 'groups_attribute_path.', + multi: true, + allowCustomValue: true, + options: [], + validation: + provider === 'azuread' + ? { + validate: (value) => { + if (typeof value === 'string') { + return uuidValidate(value); + } + if (isSelectableValue(value)) { + return value.every((v) => v?.value && uuidValidate(v.value)); + } + return true; + }, + message: 'Allowed groups must be Object Ids.', + } + : undefined, + }, + apiUrl: { + label: 'API URL', + type: 'text', + description: ( + <> + The user information endpoint of your OAuth2 provider. Information returned by this endpoint must be + compatible with{' '} + + OpenID UserInfo + + . + + ), + validation: { + required: false, + }, + }, + roleAttributePath: { + label: 'Role attribute path', + description: 'JMESPath expression to use for Grafana role lookup.', + type: 'text', + validation: { + required: false, + }, + }, + name: { + label: 'Display name', + description: 'Helpful if you use more than one identity providers or SSO protocols.', + type: 'text', + }, + allowSignUp: { + label: 'Allow sign up', + description: 'If not enabled, only existing Grafana users can log in using OAuth.', + type: 'switch', + }, + autoLogin: { + label: 'Auto login', + description: 'Log in automatically, skipping the login screen.', + type: 'switch', + }, + signoutRedirectUrl: { + label: 'Sign out redirect URL', + description: 'The URL to redirect the user to after signing out from Grafana.', + type: 'text', + validation: { + required: false, + }, + }, + emailAttributeName: { + label: 'Email attribute name', + description: 'Name of the key to use for user email lookup within the attributes map of OAuth2 ID token.', + type: 'text', + }, + emailAttributePath: { + label: 'Email attribute path', + description: 'JMESPath expression to use for user email lookup from the user information.', + type: 'text', + }, + nameAttributePath: { + label: 'Name attribute path', + description: + 'JMESPath expression to use for user name lookup from the user ID token. \n' + + 'This name will be used as the user’s display name.', + type: 'text', + }, + loginAttributePath: { + label: 'Login attribute path', + description: 'JMESPath expression to use for user login lookup from the user ID token.', + type: 'text', + }, + idTokenAttributeName: { + label: 'ID token attribute name', + description: 'The name of the key used to extract the ID token from the returned OAuth2 token.', + type: 'text', + }, + roleAttributeStrict: { + label: 'Role attribute strict mode', + description: 'If enabled, denies user login if the Grafana role cannot be extracted using Role attribute path.', + type: 'switch', + }, + allowAssignGrafanaAdmin: { + label: 'Allow assign Grafana admin', + description: 'If enabled, it will automatically sync the Grafana server administrator role.', + type: 'switch', + }, + skipOrgRoleSync: { + label: 'Skip organization role sync', + description: 'Prevent synchronizing users’ organization roles from your IdP.', + type: 'switch', + }, + defineAllowedGroups: { + label: 'Define allowed groups', + type: 'switch', + }, + defineAllowedTeamsIds: { + label: 'Define allowed teams ids', + type: 'switch', + }, + usePkce: { + label: 'Use PKCE', + description: ( + <> + If enabled, Grafana will use{' '} + + Proof Key for Code Exchange (PKCE) + {' '} + with the OAuth2 Authorization Code Grant. + + ), + type: 'checkbox', + }, + useRefreshToken: { + label: 'Use refresh token', + description: + 'If enabled, Grafana will fetch a new access token using the refresh token provided by the OAuth2 provider.', + type: 'checkbox', + }, + configureTLS: { + label: 'Configure TLS', + type: 'switch', + }, + tlsClientCa: { + label: 'TLS client ca', + description: 'The path to the trusted certificate authority list. Does not applicable on Grafana Cloud.', + type: 'text', + }, + tlsClientCert: { + label: 'TLS client cert', + description: 'The path to the certificate. Does not applicable on Grafana Cloud.', + type: 'text', + }, + tlsClientKey: { + label: 'TLS client key', + description: 'The path to the key. Does not applicable on Grafana Cloud.', + type: 'text', + }, + tlsSkipVerifyInsecure: { + label: 'TLS skip verify', + description: + 'If enabled, the client accepts any certificate presented by the server and any host \n' + + 'name in that certificate. You should only use this for testing, because this mode leaves \n' + + 'SSL/TLS susceptible to man-in-the-middle attacks.', + type: 'switch', + }, + groupsAttributePath: { + label: 'Groups attribute path', + description: + 'JMESPath expression to use for user group lookup. If you configure allowed_groups, \n' + + 'you must also configure groupsƒ_attribute_path.', + type: 'text', + }, + teamsUrl: { + label: 'Teams URL', + description: + 'The URL used to query for Team Ids. If not set, the default value is /teams. \n' + + 'If you configure teams_url, you must also configure team_ids_attribute_path.', + type: 'text', + validation: { + validate: (value, formValues) => { + if (formValues.teamIds.length) { + return !!value; + } + return true; + }, + message: 'This field must be set if Team Ids are configured.', + }, + }, + teamIdsAttributePath: { + label: 'Team Ids attribute path', + description: + 'The JMESPath expression to use for Grafana Team Id lookup within the results returned by the teams_url endpoint.', + type: 'text', + validation: { + validate: (value, formValues) => { + if (formValues.teamIds.length) { + return !!value; + } + return true; + }, + message: 'This field must be set if Team Ids are configured.', + }, + }, + teamIds: { + label: 'Team Ids', + type: 'select', + description: + 'String list of Team Ids. If set, the user must be a member of one of the given teams to log in. \n' + + 'If you configure team_ids, you must also configure teams_url and team_ids_attribute_path.', + multi: true, + allowCustomValue: true, + options: [], + placeholder: 'Enter Team Ids and press Enter to add', + validation: + provider === 'github' + ? { + validate: (value) => { + if (typeof value === 'string') { + return isNumeric(value); + } + if (isSelectableValue(value)) { + return value.every((v) => v?.value && isNumeric(v.value)); + } + return true; + }, + message: 'Team Ids must be numbers.', + } + : undefined, + }, + }; +} // Check if a string contains only numeric values function isNumeric(value: string) { diff --git a/public/app/features/auth-config/utils/data.ts b/public/app/features/auth-config/utils/data.ts index 55fdbd0fdb1..d269dcc0ae8 100644 --- a/public/app/features/auth-config/utils/data.ts +++ b/public/app/features/auth-config/utils/data.ts @@ -58,7 +58,7 @@ export function dataToDTO(data?: SSOProvider): SSOProviderDTO { if (!data) { return emptySettings; } - const arrayFields = getArrayFields(fieldMap); + const arrayFields = getArrayFields(fieldMap(data.provider)); const settings = { ...data.settings }; for (const field of arrayFields) { //@ts-expect-error @@ -89,12 +89,16 @@ const includeRequiredKeysOnly = ( // Convert the DTO to the data format used by the API export function dtoToData(dto: SSOProviderDTO, provider: string) { - const arrayFields = getArrayFields(fieldMap); - const dtoWithRequiredFields = includeRequiredKeysOnly(dto, [...fields[provider], 'enabled']); - const settings = { ...dtoWithRequiredFields }; + const arrayFields = getArrayFields(fieldMap(provider)); + let current: Partial = dto; + + if (fields[provider]) { + current = includeRequiredKeysOnly(dto, [...fields[provider], 'enabled']); + } + const settings = { ...current }; for (const field of arrayFields) { - const value = dtoWithRequiredFields[field]; + const value = current[field]; if (value) { if (isSelectableValue(value)) { //@ts-expect-error From 1d5edb2a182927657afc9bfb7f97ca7d0a5d43e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 Jan 2024 12:04:45 +0100 Subject: [PATCH 072/169] DashboardScene: Saving (#81163) * DashboardScene: First save works * Updates * version mismatch works * Error handling * save current time range working * Progress on save as * Save as works * Progress * First tests * Add unit tests * Minor tweak * Update * Update isDirty state when saving --- .betterer.results | 7 + .../api/browseDashboardsAPI.ts | 3 +- .../saving/SaveDashboardAsForm.tsx | 196 +++++++++++++++++ .../saving/SaveDashboardDrawer.test.tsx | 207 ++++++++++++++++++ .../saving/SaveDashboardDrawer.tsx | 81 +++++++ .../saving/SaveDashboardForm.tsx | 145 ++++++++++++ .../dashboard-scene/saving/shared.tsx | 112 ++++++++++ .../saving/useSaveDashboard.ts | 83 +++++++ .../dashboard-scene/scene/DashboardScene.tsx | 79 +++++-- .../scene/NavToolbarActions.tsx | 10 +- .../scene/keyboardShortcuts.ts | 5 + .../serialization/SaveDashboardDrawer.tsx | 41 ---- .../serialization/dataLayersToAnnotations.ts | 1 + .../transformSaveModelToScene.ts | 6 +- .../components/SaveDashboard/types.ts | 4 +- .../app/features/trails/DataTrailDrawer.tsx | 2 +- 16 files changed, 916 insertions(+), 66 deletions(-) create mode 100644 public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx create mode 100644 public/app/features/dashboard-scene/saving/SaveDashboardDrawer.test.tsx create mode 100644 public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx create mode 100644 public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx create mode 100644 public/app/features/dashboard-scene/saving/shared.tsx create mode 100644 public/app/features/dashboard-scene/saving/useSaveDashboard.ts delete mode 100644 public/app/features/dashboard-scene/serialization/SaveDashboardDrawer.tsx diff --git a/.betterer.results b/.betterer.results index cfc75962fc6..ee5b0e9e79d 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2401,6 +2401,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], + "public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx:5381": [ + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] + ], + "public/app/features/dashboard-scene/saving/shared.tsx:5381": [ + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] + ], "public/app/features/dashboard-scene/scene/DashboardScene.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 0324a580a28..315381d1632 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -275,9 +275,10 @@ export const browseDashboardsAPI = createApi({ }), // save an existing dashboard saveDashboard: builder.mutation({ - query: ({ dashboard, folderUid, message, overwrite }) => ({ + query: ({ dashboard, folderUid, message, overwrite, showErrorAlert }) => ({ url: `/dashboards/db`, method: 'POST', + showErrorAlert, data: { dashboard, folderUid, diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx new file mode 100644 index 00000000000..d8a72c7beba --- /dev/null +++ b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { UseFormSetValue, useForm } from 'react-hook-form'; + +import { Dashboard } from '@grafana/schema'; +import { Button, Input, Switch, Field, Label, TextArea, Stack, Alert, Box } from '@grafana/ui'; +import { FolderPicker } from 'app/core/components/Select/FolderPicker'; +import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv'; + +import { DashboardScene } from '../scene/DashboardScene'; + +import { SaveDashboardDrawer } from './SaveDashboardDrawer'; +import { DashboardChangeInfo, NameAlreadyExistsError, SaveButton, isNameExistsError } from './shared'; +import { useDashboardSave } from './useSaveDashboard'; + +interface SaveDashboardAsFormDTO { + firstName?: string; + title: string; + description: string; + folder: { uid?: string; title?: string }; + copyTags: boolean; +} + +export interface Props { + dashboard: DashboardScene; + drawer: SaveDashboardDrawer; + changeInfo: DashboardChangeInfo; +} + +export function SaveDashboardAsForm({ dashboard, drawer, changeInfo }: Props) { + const { changedSaveModel } = changeInfo; + + const { register, handleSubmit, setValue, formState, getValues } = useForm({ + mode: 'onBlur', + defaultValues: { + title: changeInfo.isNew ? changedSaveModel.title! : `${changedSaveModel.title} Copy`, + description: changedSaveModel.description ?? '', + folder: { + uid: dashboard.state.meta.folderUid, + title: dashboard.state.meta.folderTitle, + }, + copyTags: false, + }, + }); + const { errors, isValid, defaultValues } = formState; + const formValues = getValues(); + + const { state, onSaveDashboard } = useDashboardSave(false); + + const onSave = async (overwrite: boolean) => { + const data = getValues(); + + const dashboardToSave: Dashboard = getSaveAsDashboardSaveModel(changedSaveModel, data, changeInfo.isNew); + const result = await onSaveDashboard(dashboard, dashboardToSave, { overwrite, folderUid: data.folder.uid }); + + if (result.status === 'success') { + dashboard.closeModal(); + } + }; + + const cancelButton = ( + + ); + + const saveButton = (overwrite: boolean) => ( + + ); + + function renderFooter(error?: Error) { + if (isNameExistsError(error)) { + return ; + } + + return ( + <> + {error && ( + +

{error.message}

+
+ )} + + {cancelButton} + {saveButton(false)} + + + ); + } + + return ( +
onSave(false))}> + } + invalid={!!errors.title} + error={errors.title?.message} + > + + + } + invalid={!!errors.description} + error={errors.description?.message} + > +