From 9d6ab92e39b104bf9db01df240201099d3e4e427 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Wed, 1 Mar 2023 15:34:53 +0000 Subject: [PATCH] Service accounts: Remove Add API keys buttons and remove one state of migrating for API keys tab (#63411) * add: hide apikeys tab on start * make use of store method * added hiding of apikeys tab for new org creation * missing err check * removed unused files * implemennted fake to make tests run * move check for globalHideApikeys from org to admin * refactor to remove the fake * removed unused method calls for interface * Update pkg/services/serviceaccounts/manager/service.go Co-authored-by: Alexander Zobnin * Update pkg/services/serviceaccounts/manager/service.go Co-authored-by: Alexander Zobnin * remove the checkglobal method * removed duplicate global set const * add count of apikeys for performance * remove apikeys adding in UI * added back deleted file * added comment on component * changed wording and copy for hiding and migrating service accounts * refactor: remove migrationstatus in front/backend This removes the migrationstatus state from the UI in favor of only looking at the number of API keys to determine what to show to the user. This simplifies the logic and makes less calls to the backend with each page load. This was called both on the API keys page and the Service accounts page. - removes the state of migrationstatus from the UI - removes the backend call - removes the backend endpoint for migrationstatus * Update pkg/services/apikey/apikeyimpl/xorm_store.go Co-authored-by: Karl Persson * changes the contet to also be primary * change id of version for footer component --------- Co-authored-by: Alexander Zobnin Co-authored-by: Karl Persson --- .betterer.results | 3 - pkg/services/apikey/apikey.go | 1 + pkg/services/apikey/apikeyimpl/apikey.go | 3 + pkg/services/apikey/apikeyimpl/sqlx_store.go | 12 ++ pkg/services/apikey/apikeyimpl/store.go | 1 + pkg/services/apikey/apikeyimpl/xorm_store.go | 19 +++ pkg/services/apikey/apikeytest/fake.go | 4 + pkg/services/navtree/navtreeimpl/admin.go | 5 +- pkg/services/serviceaccounts/api/api.go | 12 -- pkg/services/serviceaccounts/api/api_test.go | 26 ++-- .../serviceaccounts/api/token_test.go | 6 +- .../serviceaccounts/database/store.go | 16 --- .../serviceaccounts/manager/service.go | 7 -- .../serviceaccounts/manager/service_test.go | 7 +- pkg/services/serviceaccounts/manager/store.go | 1 - pkg/services/serviceaccounts/models.go | 4 - public/app/core/components/Footer/Footer.tsx | 2 +- .../features/api-keys/APIKeysMigratedCard.tsx | 18 +-- .../features/api-keys/ApiKeysActionBar.tsx | 8 +- .../api-keys/ApiKeysAddedModal.test.tsx | 41 ------- .../features/api-keys/ApiKeysAddedModal.tsx | 55 --------- .../features/api-keys/ApiKeysController.tsx | 2 + public/app/features/api-keys/ApiKeysForm.tsx | 111 ------------------ .../features/api-keys/ApiKeysPage.test.tsx | 69 ----------- public/app/features/api-keys/ApiKeysPage.tsx | 78 ++---------- .../api-keys/MigrateToServiceAccountsCard.tsx | 18 +-- public/app/features/api-keys/state/actions.ts | 30 +---- .../app/features/api-keys/state/reducers.ts | 7 +- .../features/api-keys/state/selectors.test.ts | 10 -- .../ServiceAccountsListPage.test.tsx | 8 -- .../ServiceAccountsListPage.tsx | 29 +---- .../app/features/serviceaccounts/constants.ts | 1 - .../features/serviceaccounts/state/actions.ts | 27 ----- .../serviceaccounts/state/reducers.ts | 10 -- public/app/types/apiKeys.ts | 7 -- public/app/types/serviceaccount.ts | 2 - 36 files changed, 96 insertions(+), 564 deletions(-) delete mode 100644 public/app/features/api-keys/ApiKeysAddedModal.test.tsx delete mode 100644 public/app/features/api-keys/ApiKeysAddedModal.tsx delete mode 100644 public/app/features/api-keys/ApiKeysForm.tsx delete mode 100644 public/app/features/serviceaccounts/constants.ts diff --git a/.betterer.results b/.betterer.results index 3b2ceb4fc94..85a1ffd7e09 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2795,9 +2795,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "5"], [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], - "public/app/features/api-keys/ApiKeysForm.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/canvas/element.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/pkg/services/apikey/apikey.go b/pkg/services/apikey/apikey.go index a2ac556704b..ccdd3454ace 100644 --- a/pkg/services/apikey/apikey.go +++ b/pkg/services/apikey/apikey.go @@ -7,6 +7,7 @@ import ( type Service interface { GetAPIKeys(ctx context.Context, query *GetApiKeysQuery) error GetAllAPIKeys(ctx context.Context, orgID int64) ([]*APIKey, error) + CountAPIKeys(ctx context.Context, orgID int64) (int64, error) DeleteApiKey(ctx context.Context, cmd *DeleteCommand) error AddAPIKey(ctx context.Context, cmd *AddCommand) error GetApiKeyById(ctx context.Context, query *GetByIDQuery) error diff --git a/pkg/services/apikey/apikeyimpl/apikey.go b/pkg/services/apikey/apikeyimpl/apikey.go index 2a09d26319f..1472662628e 100644 --- a/pkg/services/apikey/apikeyimpl/apikey.go +++ b/pkg/services/apikey/apikeyimpl/apikey.go @@ -68,6 +68,9 @@ func (s *Service) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error { func (s *Service) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error { return s.store.UpdateAPIKeyLastUsedDate(ctx, tokenID) } +func (s *Service) CountAPIKeys(ctx context.Context, orgID int64) (int64, error) { + return s.store.CountAPIKeys(ctx, orgID) +} func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { limits := "a.Map{} diff --git a/pkg/services/apikey/apikeyimpl/sqlx_store.go b/pkg/services/apikey/apikeyimpl/sqlx_store.go index 481b73360d1..7b43490ba48 100644 --- a/pkg/services/apikey/apikeyimpl/sqlx_store.go +++ b/pkg/services/apikey/apikeyimpl/sqlx_store.go @@ -63,6 +63,18 @@ func (ss *sqlxStore) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey. return result, err } +func (ss *sqlxStore) CountAPIKeys(ctx context.Context, orgID int64) (int64, error) { + type result struct { + Count int64 + } + r := result{} + err := ss.sess.Get(ctx, &r, `SELECT COUNT(*) AS count FROM api_key WHERE service_account_id IS NULL and org_id = ?`, orgID) + if err != nil { + return 0, err + } + return r.Count, err +} + func (ss *sqlxStore) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error { res, err := ss.sess.Exec(ctx, "DELETE FROM api_key WHERE id=? and org_id=? and service_account_id IS NULL", cmd.ID, cmd.OrgID) if err != nil { diff --git a/pkg/services/apikey/apikeyimpl/store.go b/pkg/services/apikey/apikeyimpl/store.go index 54988660d08..fa3c33cae29 100644 --- a/pkg/services/apikey/apikeyimpl/store.go +++ b/pkg/services/apikey/apikeyimpl/store.go @@ -10,6 +10,7 @@ import ( type store interface { GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.APIKey, error) + CountAPIKeys(ctx context.Context, orgID int64) (int64, error) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuery) error diff --git a/pkg/services/apikey/apikeyimpl/xorm_store.go b/pkg/services/apikey/apikeyimpl/xorm_store.go index 73298cc15c8..61b521a0b56 100644 --- a/pkg/services/apikey/apikeyimpl/xorm_store.go +++ b/pkg/services/apikey/apikeyimpl/xorm_store.go @@ -65,6 +65,25 @@ func (ss *sqlStore) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.A return result, err } +func (ss *sqlStore) CountAPIKeys(ctx context.Context, orgID int64) (int64, error) { + type result struct { + Count int64 + } + + r := result{} + err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + rawSQL := "SELECT COUNT(*) AS count FROM api_key WHERE org_id = ? and service_account_id IS NULL" + if _, err := sess.SQL(rawSQL, orgID).Get(&r); err != nil { + return err + } + return nil + }) + if err != nil { + return 0, err + } + return r.Count, err +} + func (ss *sqlStore) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error { return ss.db.WithDbSession(ctx, func(sess *db.Session) error { rawSQL := "DELETE FROM api_key WHERE id=? and org_id=? and service_account_id IS NULL" diff --git a/pkg/services/apikey/apikeytest/fake.go b/pkg/services/apikey/apikeytest/fake.go index 35ed0ec76e0..00f14b326f6 100644 --- a/pkg/services/apikey/apikeytest/fake.go +++ b/pkg/services/apikey/apikeytest/fake.go @@ -8,6 +8,7 @@ import ( type Service struct { ExpectedError error + ExpectedCount int64 ExpectedAPIKeys []*apikey.APIKey ExpectedAPIKey *apikey.APIKey } @@ -19,6 +20,9 @@ func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.APIKey, error) { return s.ExpectedAPIKeys, s.ExpectedError } +func (s *Service) CountAPIKeys(ctx context.Context, orgID int64) (int64, error) { + return s.ExpectedCount, s.ExpectedError +} func (s *Service) GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuery) error { query.Result = s.ExpectedAPIKey return s.ExpectedError diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 4c995cdbf5b..6f6e9164a15 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -80,12 +80,13 @@ func (s *ServiceImpl) getOrgAdminNode(c *contextmodel.ReqContext) (*navtree.NavL } hideApiKeys, _, _ := s.kvStore.Get(c.Req.Context(), c.OrgID, "serviceaccounts", "hideApiKeys") - apiKeys, err := s.apiKeyService.GetAllAPIKeys(c.Req.Context(), c.OrgID) + apiKeys, err := s.apiKeyService.CountAPIKeys(c.Req.Context(), c.OrgID) if err != nil { return nil, err } - apiKeysHidden := hideApiKeys == "1" && len(apiKeys) == 0 + // Hide API keys if the global setting is set or if the org setting is set and there are no API keys + apiKeysHidden := hideApiKeys == "1" && apiKeys == 0 if hasAccess(ac.ReqOrgAdmin, ac.ApiKeyAccessEvaluator) && !apiKeysHidden { configNodes = append(configNodes, &navtree.NavLink{ Text: "API keys", diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index b11665388ca..706a61eb8be 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -41,7 +41,6 @@ type service interface { SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error - GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error) HideApiKeysTab(ctx context.Context, orgID int64) error MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error @@ -89,8 +88,6 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints() { accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.CreateToken)) serviceAccountsRoute.Delete("/:serviceAccountId/tokens/:tokenId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteToken)) - serviceAccountsRoute.Get("/migrationstatus", auth(middleware.ReqOrgAdmin, - accesscontrol.EvalPermission(serviceaccounts.ActionRead)), routing.Wrap(api.GetAPIKeysMigrationStatus)) serviceAccountsRoute.Post("/hideApiKeys", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.HideApiKeysTab)) serviceAccountsRoute.Post("/migrate", auth(middleware.ReqOrgAdmin, @@ -364,15 +361,6 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *contextmode return response.JSON(http.StatusOK, serviceAccountSearch) } -// GET /api/serviceaccounts/migrationstatus -func (api *ServiceAccountsAPI) GetAPIKeysMigrationStatus(ctx *contextmodel.ReqContext) response.Response { - upgradeStatus, err := api.service.GetAPIKeysMigrationStatus(ctx.Req.Context(), ctx.OrgID) - if err != nil { - return response.Error(http.StatusInternalServerError, "Internal server error", err) - } - return response.JSON(http.StatusOK, upgradeStatus) -} - // POST /api/serviceaccounts/hideapikeys func (api *ServiceAccountsAPI) HideApiKeysTab(ctx *contextmodel.ReqContext) response.Response { if err := api.service.HideApiKeysTab(ctx.Req.Context(), ctx.OrgID); err != nil { diff --git a/pkg/services/serviceaccounts/api/api_test.go b/pkg/services/serviceaccounts/api/api_test.go index f6f97f40928..7dc75acc2be 100644 --- a/pkg/services/serviceaccounts/api/api_test.go +++ b/pkg/services/serviceaccounts/api/api_test.go @@ -81,7 +81,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t, func(a *ServiceAccountsAPI) { - a.service = &fakeService{ExpectedServiceAccount: tt.expectedSA, ExpectedErr: tt.expectedErr} + a.service = &fakeServiceAccountService{ExpectedServiceAccount: tt.expectedSA, ExpectedErr: tt.expectedErr} }) req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/", strings.NewReader(tt.body)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) @@ -159,7 +159,7 @@ func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t, func(a *ServiceAccountsAPI) { - a.service = &fakeService{ExpectedServiceAccountProfile: tt.expectedSA} + a.service = &fakeServiceAccountService{ExpectedServiceAccountProfile: tt.expectedSA} }) req := server.NewGetRequest(fmt.Sprintf("/api/serviceaccounts/%d", tt.id)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) @@ -221,7 +221,7 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t, func(a *ServiceAccountsAPI) { - a.service = &fakeService{ExpectedServiceAccountProfile: tt.expectedSA} + a.service = &fakeServiceAccountService{ExpectedServiceAccountProfile: tt.expectedSA} }) req := server.NewRequest(http.MethodPatch, fmt.Sprintf("/api/serviceaccounts/%d", tt.id), strings.NewReader(tt.body)) @@ -240,7 +240,7 @@ func setupTests(t *testing.T, opts ...func(a *ServiceAccountsAPI)) *webtest.Serv cfg := setting.NewCfg() api := &ServiceAccountsAPI{ cfg: cfg, - service: &fakeService{}, + service: &fakeServiceAccountService{}, accesscontrolService: &actest.FakeService{}, accesscontrol: acimpl.ProvideAccessControl(cfg), RouterRegister: routing.NewRouteRegister(), @@ -255,9 +255,9 @@ func setupTests(t *testing.T, opts ...func(a *ServiceAccountsAPI)) *webtest.Serv return webtest.NewServer(t, api.RouterRegister) } -var _ service = new(fakeService) +var _ service = new(fakeServiceAccountService) -type fakeService struct { +type fakeServiceAccountService struct { service ExpectedErr error ExpectedAPIKey *apikey.APIKey @@ -266,30 +266,30 @@ type fakeService struct { ExpectedServiceAccountProfile *serviceaccounts.ServiceAccountProfileDTO } -func (f *fakeService) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) { +func (f *fakeServiceAccountService) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) { return f.ExpectedServiceAccount, f.ExpectedErr } -func (f *fakeService) DeleteServiceAccount(ctx context.Context, orgID, id int64) error { +func (f *fakeServiceAccountService) DeleteServiceAccount(ctx context.Context, orgID, id int64) error { return f.ExpectedErr } -func (f *fakeService) RetrieveServiceAccount(ctx context.Context, orgID, id int64) (*serviceaccounts.ServiceAccountProfileDTO, error) { +func (f *fakeServiceAccountService) RetrieveServiceAccount(ctx context.Context, orgID, id int64) (*serviceaccounts.ServiceAccountProfileDTO, error) { return f.ExpectedServiceAccountProfile, f.ExpectedErr } -func (f *fakeService) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) { +func (f *fakeServiceAccountService) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) { return f.ExpectedServiceAccountTokens, f.ExpectedErr } -func (f *fakeService) UpdateServiceAccount(ctx context.Context, orgID, id int64, cmd *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) { +func (f *fakeServiceAccountService) UpdateServiceAccount(ctx context.Context, orgID, id int64, cmd *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) { return f.ExpectedServiceAccountProfile, f.ExpectedErr } -func (f *fakeService) AddServiceAccountToken(ctx context.Context, id int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) (*apikey.APIKey, error) { +func (f *fakeServiceAccountService) AddServiceAccountToken(ctx context.Context, id int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) (*apikey.APIKey, error) { return f.ExpectedAPIKey, f.ExpectedErr } -func (f *fakeService) DeleteServiceAccountToken(ctx context.Context, orgID, id, tokenID int64) error { +func (f *fakeServiceAccountService) DeleteServiceAccountToken(ctx context.Context, orgID, id, tokenID int64) error { return f.ExpectedErr } diff --git a/pkg/services/serviceaccounts/api/token_test.go b/pkg/services/serviceaccounts/api/token_test.go index 79ce0ef1bb7..bcce9ebf9bb 100644 --- a/pkg/services/serviceaccounts/api/token_test.go +++ b/pkg/services/serviceaccounts/api/token_test.go @@ -43,7 +43,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t, func(a *ServiceAccountsAPI) { - a.service = &fakeService{} + a.service = &fakeServiceAccountService{} }) req := server.NewGetRequest(fmt.Sprintf("/api/serviceaccounts/%d/tokens", tt.id)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) @@ -109,7 +109,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t, func(a *ServiceAccountsAPI) { a.cfg.ApiKeyMaxSecondsToLive = tt.tokenTTL - a.service = &fakeService{ + a.service = &fakeServiceAccountService{ ExpectedErr: tt.expectedErr, ExpectedAPIKey: tt.expectedAPIKey, } @@ -163,7 +163,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t, func(a *ServiceAccountsAPI) { - a.service = &fakeService{ExpectedErr: tt.expectedErr} + a.service = &fakeServiceAccountService{ExpectedErr: tt.expectedErr} }) req := server.NewRequest(http.MethodDelete, fmt.Sprintf("/api/serviceaccounts/%d/tokens/%d", tt.saID, tt.apikeyID), nil) diff --git a/pkg/services/serviceaccounts/database/store.go b/pkg/services/serviceaccounts/database/store.go index c9e5b05cf13..c2a16aa436a 100644 --- a/pkg/services/serviceaccounts/database/store.go +++ b/pkg/services/serviceaccounts/database/store.go @@ -388,22 +388,6 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, return searchResult, nil } -func (s *ServiceAccountsStoreImpl) GetAPIKeysMigrationStatus(ctx context.Context, orgId int64) (status *serviceaccounts.APIKeysMigrationStatus, err error) { - migrationStatus, exists, err := s.kvStore.Get(ctx, orgId, "serviceaccounts", "migrationStatus") - if err != nil { - return nil, err - } - if exists && migrationStatus == "1" { - return &serviceaccounts.APIKeysMigrationStatus{ - Migrated: true, - }, nil - } else { - return &serviceaccounts.APIKeysMigrationStatus{ - Migrated: false, - }, nil - } -} - func (s *ServiceAccountsStoreImpl) HideApiKeysTab(ctx context.Context, orgId int64) error { if err := s.kvStore.Set(ctx, orgId, "serviceaccounts", "hideApiKeys", "1"); err != nil { s.log.Error("Failed to hide API keys tab", err) diff --git a/pkg/services/serviceaccounts/manager/service.go b/pkg/services/serviceaccounts/manager/service.go index 74560bfb7f8..031b4c7bcde 100644 --- a/pkg/services/serviceaccounts/manager/service.go +++ b/pkg/services/serviceaccounts/manager/service.go @@ -226,13 +226,6 @@ func (sa *ServiceAccountsService) DeleteServiceAccountToken(ctx context.Context, return sa.store.DeleteServiceAccountToken(ctx, orgID, serviceAccountID, tokenID) } -func (sa *ServiceAccountsService) GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (status *serviceaccounts.APIKeysMigrationStatus, err error) { - if err := validOrgID(orgID); err != nil { - return nil, err - } - return sa.store.GetAPIKeysMigrationStatus(ctx, orgID) -} - func (sa *ServiceAccountsService) HideApiKeysTab(ctx context.Context, orgID int64) error { if err := validOrgID(orgID); err != nil { return err diff --git a/pkg/services/serviceaccounts/manager/service_test.go b/pkg/services/serviceaccounts/manager/service_test.go index 1e5d68a4b55..9d7f806385c 100644 --- a/pkg/services/serviceaccounts/manager/service_test.go +++ b/pkg/services/serviceaccounts/manager/service_test.go @@ -17,10 +17,10 @@ type FakeServiceAccountStore struct { ExpectedServiceAccountDTO *serviceaccounts.ServiceAccountDTO ExpectedServiceAccountProfileDTO *serviceaccounts.ServiceAccountProfileDTO ExpectedSearchServiceAccountQueryResult *serviceaccounts.SearchOrgServiceAccountsResult - ExpectedServiceAccountMigrationStatus *serviceaccounts.APIKeysMigrationStatus ExpectedStats *serviceaccounts.Stats ExpectedAPIKeys []apikey.APIKey ExpectedAPIKey *apikey.APIKey + ExpectedBoolean bool ExpectedError error } @@ -59,11 +59,6 @@ func (f *FakeServiceAccountStore) DeleteServiceAccount(ctx context.Context, orgI return f.ExpectedError } -// GetAPIKeysMigrationStatus is a fake getting the api keys migration status. -func (f *FakeServiceAccountStore) GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error) { - return f.ExpectedServiceAccountMigrationStatus, f.ExpectedError -} - // HideApiKeysTab is a fake hiding the api keys tab. func (f *FakeServiceAccountStore) HideApiKeysTab(ctx context.Context, orgID int64) error { return f.ExpectedError diff --git a/pkg/services/serviceaccounts/manager/store.go b/pkg/services/serviceaccounts/manager/store.go index 7ddb53ceb34..0168d334366 100644 --- a/pkg/services/serviceaccounts/manager/store.go +++ b/pkg/services/serviceaccounts/manager/store.go @@ -26,7 +26,6 @@ type store interface { RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error - GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error) HideApiKeysTab(ctx context.Context, orgID int64) error MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error diff --git a/pkg/services/serviceaccounts/models.go b/pkg/services/serviceaccounts/models.go index 68a7ab64411..1239cb72650 100644 --- a/pkg/services/serviceaccounts/models.go +++ b/pkg/services/serviceaccounts/models.go @@ -130,10 +130,6 @@ type ServiceAccountProfileDTO struct { type ServiceAccountFilter string // used for filtering -type APIKeysMigrationStatus struct { - Migrated bool `json:"migrated"` -} - const ( FilterOnlyExpiredTokens ServiceAccountFilter = "expiredTokens" FilterOnlyDisabled ServiceAccountFilter = "disabled" diff --git a/public/app/core/components/Footer/Footer.tsx b/public/app/core/components/Footer/Footer.tsx index 0b9e873d351..3ebe9e3b733 100644 --- a/public/app/core/components/Footer/Footer.tsx +++ b/public/app/core/components/Footer/Footer.tsx @@ -55,7 +55,7 @@ export function getVersionLinks(): FooterLink[] { links.push({ target: '_blank', - id: 'version', + id: 'license', text: `${buildInfo.edition}${stateInfo}`, url: licenseInfo.licenseUrl, }); diff --git a/public/app/features/api-keys/APIKeysMigratedCard.tsx b/public/app/features/api-keys/APIKeysMigratedCard.tsx index c2bfba21093..a6cd305c6e7 100644 --- a/public/app/features/api-keys/APIKeysMigratedCard.tsx +++ b/public/app/features/api-keys/APIKeysMigratedCard.tsx @@ -6,29 +6,31 @@ import { Alert, ConfirmModal, useStyles2, Button } from '@grafana/ui'; interface Props { onHideApiKeys: () => void; + apikeys: number; } -export const APIKeysMigratedCard = ({ onHideApiKeys }: Props): JSX.Element => { +export const APIKeysMigratedCard = ({ onHideApiKeys, apikeys }: Props): JSX.Element => { const [isModalOpen, setIsModalOpen] = useState(false); const styles = useStyles2(getStyles); return ( - +
- We have migrated API keys into Grafana service accounts. All API keys are safe and continue working as they used - to, you can find them inside the respective service account. + Migrated API keys are safe and continue working as they used to. You can find them inside the respective service + account.
- setIsModalOpen(false)} + confirmButtonVariant="primary" /> View service accounts page
diff --git a/public/app/features/api-keys/ApiKeysActionBar.tsx b/public/app/features/api-keys/ApiKeysActionBar.tsx index ef182d1c799..17642fe2b24 100644 --- a/public/app/features/api-keys/ApiKeysActionBar.tsx +++ b/public/app/features/api-keys/ApiKeysActionBar.tsx @@ -1,23 +1,19 @@ import React, { FC } from 'react'; -import { Button, FilterInput } from '@grafana/ui'; +import { FilterInput } from '@grafana/ui'; interface Props { searchQuery: string; disabled: boolean; - onAddClick: () => void; onSearchChange: (value: string) => void; } -export const ApiKeysActionBar: FC = ({ searchQuery, disabled, onAddClick, onSearchChange }) => { +export const ApiKeysActionBar: FC = ({ searchQuery, disabled, onSearchChange }) => { return (
-
); }; diff --git a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx deleted file mode 100644 index b6342923402..00000000000 --- a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { ApiKeysAddedModal, Props } from './ApiKeysAddedModal'; - -describe('ApiKeysAddedModal', () => { - const props: Props = { - onDismiss: jest.fn(), - apiKey: 'myApiKey', - rootPath: 'test/path', - }; - - it('should render without throwing', () => { - expect(() => render()).not.toThrow(); - }); - - it('displays the apiKey in a readOnly input', () => { - render(); - const input = screen.getByRole('textbox'); - expect(input).toHaveValue(props.apiKey); - expect(input).toHaveAttribute('readonly'); - }); - - it('has a `Copy to clipboard` button', () => { - render(); - expect(screen.getByRole('button', { name: 'Copy' })).toBeInTheDocument(); - }); - - it('displays the correct curl path', () => { - render(); - expect( - screen.getByText('curl -H "Authorization: Bearer myApiKey" test/path/api/dashboards/home') - ).toBeInTheDocument(); - }); - - it('calls onDismiss when the modal is closed', () => { - render(); - screen.getByRole('button', { name: 'Close dialogue' }).click(); - expect(props.onDismiss).toHaveBeenCalled(); - }); -}); diff --git a/public/app/features/api-keys/ApiKeysAddedModal.tsx b/public/app/features/api-keys/ApiKeysAddedModal.tsx deleted file mode 100644 index ce04d4bd7ef..00000000000 --- a/public/app/features/api-keys/ApiKeysAddedModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { css } from '@emotion/css'; -import React, { useCallback } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, Field, Modal, useStyles2, Input, ClipboardButton } from '@grafana/ui'; - -export interface Props { - onDismiss: () => void; - apiKey: string; - rootPath: string; -} - -export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.Element { - const styles = useStyles2(getStyles); - const getClipboardText = useCallback(() => apiKey, [apiKey]); - - return ( - - - - Copy - - } - /> - - - It is not stored in this form, so be sure to copy it now. - - -

You can authenticate a request using the Authorization HTTP header, example:

-
-        curl -H "Authorization: Bearer {apiKey}" {rootPath}/api/dashboards/home
-      
-
- ); -} - -function getStyles(theme: GrafanaTheme2) { - return { - label: css` - padding: ${theme.spacing(1)}; - background-color: ${theme.colors.background.secondary}; - border-radius: ${theme.shape.borderRadius()}; - `, - small: css` - font-size: ${theme.typography.bodySmall.fontSize}; - font-weight: ${theme.typography.bodySmall.fontWeight}; - `, - }; -} diff --git a/public/app/features/api-keys/ApiKeysController.tsx b/public/app/features/api-keys/ApiKeysController.tsx index 69ac05514cb..6b1c65c3b6e 100644 --- a/public/app/features/api-keys/ApiKeysController.tsx +++ b/public/app/features/api-keys/ApiKeysController.tsx @@ -10,6 +10,8 @@ interface Props { } export const ApiKeysController: FC = ({ children }) => { + // FIXME(eleijonmarck): could not remove state from this component + // as component cannot render properly without it const [isAdding, setIsAdding] = useState(false); const toggleIsAdding = useCallback(() => { setIsAdding(!isAdding); diff --git a/public/app/features/api-keys/ApiKeysForm.tsx b/public/app/features/api-keys/ApiKeysForm.tsx deleted file mode 100644 index 973c4a9004f..00000000000 --- a/public/app/features/api-keys/ApiKeysForm.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { ChangeEvent, FC, FormEvent, useEffect, useState } from 'react'; - -import { rangeUtil, SelectableValue } from '@grafana/data'; -import { EventsWithValidation, LegacyForms, ValidationEvents, Button, Select, InlineField } from '@grafana/ui'; -import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; - -import { SlideDown } from '../../core/components/Animations/SlideDown'; -import { NewApiKey, OrgRole } from '../../types'; - -const { Input } = LegacyForms; -const ROLE_OPTIONS: Array> = Object.keys(OrgRole).map((role) => ({ - label: role, - value: role as OrgRole, -})); - -interface Props { - show: boolean; - onClose: () => void; - onKeyAdded: (apiKey: NewApiKey) => void; - disabled: boolean; -} - -function isValidInterval(value: string): boolean { - if (!value) { - return true; - } - try { - rangeUtil.intervalToSeconds(value); - return true; - } catch {} - return false; -} - -const timeRangeValidationEvents: ValidationEvents = { - [EventsWithValidation.onBlur]: [ - { - rule: isValidInterval, - errorMessage: 'Not a valid duration', - }, - ], -}; - -const tooltipText = - 'The API key life duration. For example, 1d if your key is going to last for one day. Supported units are: s,m,h,d,w,M,y'; - -export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded, disabled }) => { - const [name, setName] = useState(''); - const [role, setRole] = useState(OrgRole.Viewer); - const [secondsToLive, setSecondsToLive] = useState(''); - useEffect(() => { - setName(''); - setRole(OrgRole.Viewer); - setSecondsToLive(''); - }, [show]); - - const onSubmit = (event: FormEvent) => { - event.preventDefault(); - if (isValidInterval(secondsToLive)) { - onKeyAdded({ name, role, secondsToLive }); - onClose(); - } - }; - const onNameChange = (event: ChangeEvent) => { - setName(event.currentTarget.value); - }; - const onRoleChange = (role: SelectableValue) => { - setRole(role.value!); - }; - const onSecondsToLiveChange = (event: ChangeEvent) => { - setSecondsToLive(event.currentTarget.value); - }; - - return ( - -
- -
-
Add API Key
-
-
- Key name - -
-
- - - -
-
- -
-
-
-
-
- ); -}; diff --git a/public/app/features/api-keys/ApiKeysPage.test.tsx b/public/app/features/api-keys/ApiKeysPage.test.tsx index df8cf679c4c..f4369e75487 100644 --- a/public/app/features/api-keys/ApiKeysPage.test.tsx +++ b/public/app/features/api-keys/ApiKeysPage.test.tsx @@ -3,7 +3,6 @@ import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event' import React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; -import { selectors } from '@grafana/e2e-selectors'; import { ApiKey, OrgRole } from 'app/types'; import { mockToolkitActionCreator } from '../../../test/core/redux/mocks'; @@ -30,7 +29,6 @@ const setup = (propOverrides: Partial) => { const migrateAllMock = jest.fn(); const toggleIncludeExpiredMock = jest.fn(); const setSearchQueryMock = mockToolkitActionCreator(setSearchQuery); - const getApiKeysMigrationStatusMock = jest.fn(); const hideApiKeysMock = jest.fn(); const props: Props = { apiKeys: [] as ApiKey[], @@ -39,8 +37,6 @@ const setup = (propOverrides: Partial) => { loadApiKeys: loadApiKeysMock, deleteApiKey: deleteApiKeyMock, setSearchQuery: setSearchQueryMock, - addApiKey: addApiKeyMock, - getApiKeysMigrationStatus: getApiKeysMigrationStatusMock, migrateApiKey: migrateApiKeyMock, migrateAll: migrateAllMock, hideApiKeys: hideApiKeysMock, @@ -50,7 +46,6 @@ const setup = (propOverrides: Partial) => { includeExpiredDisabled: false, toggleIncludeExpired: toggleIncludeExpiredMock, canCreate: true, - apiKeysMigrated: false, }; Object.assign(props, propOverrides); @@ -87,13 +82,6 @@ describe('ApiKeysPage', () => { }); }); - describe('when there are no API keys', () => { - it('then it should render CTA', () => { - setup({ apiKeys: getMultipleMockKeys(0), apiKeysCount: 0, hasFetched: true }); - expect(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key'))).toBeInTheDocument(); - }); - }); - describe('when there are API keys', () => { it('then it should render API keys table', async () => { const apiKeys = [ @@ -165,66 +153,9 @@ describe('ApiKeysPage', () => { expect(deleteApiKeyMock).toHaveBeenCalledWith(2); }); }); - - describe('when a user adds an API key from CTA', () => { - it('then it should call addApiKey with correct parameters', async () => { - const apiKeys: ApiKey[] = []; - const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); - - addApiKeyMock.mockClear(); - await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key'))); - await addAndVerifyApiKey(addApiKeyMock); - }); - }); - - describe('when a user adds an API key from Add API key', () => { - it('then it should call addApiKey with correct parameters', async () => { - const apiKeys = getMultipleMockKeys(1); - const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); - - addApiKeyMock.mockClear(); - await userEvent.click(screen.getByRole('button', { name: /add api key/i })); - await addAndVerifyApiKey(addApiKeyMock); - - await toggleShowExpired(); - - addApiKeyMock.mockClear(); - await userEvent.click(screen.getByRole('button', { name: /add api key/i })); - await addAndVerifyApiKey(addApiKeyMock); - }); - }); - - describe('when a user adds an API key with an invalid expiration', () => { - it('then it should display a message', async () => { - const apiKeys = getMultipleMockKeys(1); - const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); - - addApiKeyMock.mockClear(); - await userEvent.click(screen.getByRole('button', { name: /add api key/i })); - await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test'); - await userEvent.type(screen.getByPlaceholderText(/1d/i), '60x'); - expect(screen.queryByText(/not a valid duration/i)).not.toBeInTheDocument(); - await userEvent.click(screen.getByRole('button', { name: /^add$/i })); - expect(screen.getByText(/not a valid duration/i)).toBeInTheDocument(); - expect(addApiKeyMock).toHaveBeenCalledTimes(0); - }); - }); }); async function toggleShowExpired() { expect(screen.queryByLabelText(/include expired keys/i)).toBeInTheDocument(); await userEvent.click(screen.getByLabelText(/include expired keys/i)); } - -async function addAndVerifyApiKey(addApiKeyMock: jest.Mock) { - expect(screen.getByRole('heading', { name: /add api key/i })).toBeInTheDocument(); - expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(/1d/i)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /^add$/i })).toBeInTheDocument(); - - await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test'); - await userEvent.type(screen.getByPlaceholderText(/1d/i), '60s'); - await userEvent.click(screen.getByRole('button', { name: /^add$/i })); - expect(addApiKeyMock).toHaveBeenCalledTimes(1); - expect(addApiKeyMock).toHaveBeenCalledWith({ name: 'Test', role: 'Viewer', secondsToLive: 60 }, expect.anything()); -} diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index c3030d4e2bd..012441f2990 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -2,33 +2,24 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; // Utils -import { rangeUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { InlineField, InlineSwitch, VerticalGroup } from '@grafana/ui'; -import appEvents from 'app/core/app_events'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { Page } from 'app/core/components/Page/Page'; -import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { getTimeZone } from 'app/features/profile/state/selectors'; -import { AccessControlAction, ApiKey, NewApiKey, StoreState } from 'app/types'; -import { ShowModalReactEvent } from 'app/types/events'; +import { AccessControlAction, ApiKey, StoreState } from 'app/types'; import { APIKeysMigratedCard } from './APIKeysMigratedCard'; import { ApiKeysActionBar } from './ApiKeysActionBar'; -import { ApiKeysAddedModal } from './ApiKeysAddedModal'; import { ApiKeysController } from './ApiKeysController'; -import { ApiKeysForm } from './ApiKeysForm'; import { ApiKeysTable } from './ApiKeysTable'; import { MigrateToServiceAccountsCard } from './MigrateToServiceAccountsCard'; import { - addApiKey, deleteApiKey, migrateApiKey, migrateAll, loadApiKeys, toggleIncludeExpired, - getApiKeysMigrationStatus, hideApiKeys, } from './state/actions'; import { setSearchQuery } from './state/reducers'; @@ -46,7 +37,6 @@ function mapStateToProps(state: StoreState) { includeExpired: getIncludeExpired(state.apiKeys), includeExpiredDisabled: getIncludeExpiredDisabled(state.apiKeys), canCreate: canCreate, - apiKeysMigrated: state.apiKeys.apiKeysMigrated, }; } @@ -61,8 +51,6 @@ const mapDispatchToProps = { migrateAll, setSearchQuery, toggleIncludeExpired, - addApiKey, - getApiKeysMigrationStatus, hideApiKeys, }; @@ -83,7 +71,6 @@ export class ApiKeysPageUnconnected extends PureComponent { componentDidMount() { this.fetchApiKeys(); - this.props.getApiKeysMigrationStatus(); } async fetchApiKeys() { @@ -110,40 +97,6 @@ export class ApiKeysPageUnconnected extends PureComponent { this.props.toggleIncludeExpired(); }; - onAddApiKey = (newApiKey: NewApiKey) => { - const openModal = (apiKey: string) => { - const rootPath = window.location.origin + config.appSubUrl; - - appEvents.publish( - new ShowModalReactEvent({ - props: { - apiKey, - rootPath, - }, - component: ApiKeysAddedModal, - }) - ); - }; - - const secondsToLive = newApiKey.secondsToLive; - try { - const secondsToLiveAsNumber = secondsToLive ? rangeUtil.intervalToSeconds(secondsToLive) : null; - const apiKey: ApiKey = { - ...newApiKey, - secondsToLive: secondsToLiveAsNumber, - }; - this.props.addApiKey(apiKey, openModal); - this.setState((prevState: State) => { - return { - ...prevState, - isAdding: false, - }; - }); - } catch (err) { - console.error(err); - } - }; - onHideApiKeys = async () => { try { await this.props.hideApiKeys(); @@ -165,7 +118,6 @@ export class ApiKeysPageUnconnected extends PureComponent { includeExpired, includeExpiredDisabled, canCreate, - apiKeysMigrated, } = this.props; if (!hasFetched) { @@ -180,37 +132,21 @@ export class ApiKeysPageUnconnected extends PureComponent { - {({ isAdding, toggleIsAdding }) => { - const showCTA = !isAdding && apiKeysCount === 0 && !apiKeysMigrated; + {({}) => { const showTable = apiKeysCount > 0; return ( <> - {!apiKeysMigrated && } - {apiKeysMigrated && } - {showCTA ? ( - - ) : null} + {apiKeysCount !== 0 && } + {apiKeysCount === 0 && ( + + )} {showTable ? ( ) : null} - {showTable ? ( diff --git a/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx b/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx index 6721907a709..b76e470e913 100644 --- a/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx +++ b/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx @@ -16,26 +16,24 @@ export const MigrateToServiceAccountsCard = ({ onMigrate, disabled }: Props): JS const docsLink = ( - here. + Find out more about the migration here. ); - const migrationBoxDesc = ( - Are you sure you want to migrate all API keys to service accounts? Find out more {docsLink} - ); + const migrationBoxDesc = Are you sure you want to migrate all API keys to service accounts? {docsLink}; return ( - +
- Each API key will be automatically migrated into a service account with a token. The service account will be - created with the same permission as the API Key and current API Keys will continue to work as they were. + We will soon deprecate API keys. Each API key will be migrated into a service account with a token and will + continue to work as they were. We encourage you to migrate your API keys to service accounts now. {docsLink}
setIsModalOpen(false)} + confirmVariant="primary" + confirmButtonVariant="primary" />
diff --git a/public/app/features/api-keys/state/actions.ts b/public/app/features/api-keys/state/actions.ts index 381fc17e385..65f90b415cf 100644 --- a/public/app/features/api-keys/state/actions.ts +++ b/public/app/features/api-keys/state/actions.ts @@ -1,24 +1,7 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; -import store from 'app/core/store'; -import { API_KEYS_MIGRATION_INFO_STORAGE_KEY } from 'app/features/serviceaccounts/constants'; -import { ApiKey, ThunkResult } from 'app/types'; +import { ThunkResult } from 'app/types'; -import { - apiKeysLoaded, - includeExpiredToggled, - isFetching, - apiKeysMigrationStatusLoaded, - setSearchQuery, -} from './reducers'; - -export function addApiKey(apiKey: ApiKey, openModal: (key: string) => void): ThunkResult { - return async (dispatch) => { - const result = await getBackendSrv().post('/api/auth/keys', apiKey); - dispatch(setSearchQuery('')); - dispatch(loadApiKeys()); - openModal(result.key); - }; -} +import { apiKeysLoaded, includeExpiredToggled, isFetching } from './reducers'; export function loadApiKeys(): ThunkResult { return async (dispatch) => { @@ -53,21 +36,12 @@ export function migrateAll(): ThunkResult { return async (dispatch) => { try { await getBackendSrv().post('/api/serviceaccounts/migrate'); - store.set(API_KEYS_MIGRATION_INFO_STORAGE_KEY, true); } finally { - dispatch(getApiKeysMigrationStatus()); dispatch(loadApiKeys()); } }; } -export function getApiKeysMigrationStatus(): ThunkResult { - return async (dispatch) => { - const result = await getBackendSrv().get('/api/serviceaccounts/migrationstatus'); - dispatch(apiKeysMigrationStatusLoaded(!!result?.migrated)); - }; -} - export function hideApiKeys(): ThunkResult { return async (dispatch) => { await getBackendSrv().post('/api/serviceaccounts/hideApiKeys'); diff --git a/public/app/features/api-keys/state/reducers.ts b/public/app/features/api-keys/state/reducers.ts index 4e51b74ba56..327d18d3abc 100644 --- a/public/app/features/api-keys/state/reducers.ts +++ b/public/app/features/api-keys/state/reducers.ts @@ -8,7 +8,6 @@ export const initialApiKeysState: ApiKeysState = { keys: [], keysIncludingExpired: [], searchQuery: '', - apiKeysMigrated: false, }; const apiKeysSlice = createSlice({ @@ -23,9 +22,6 @@ const apiKeysSlice = createSlice({ : state.includeExpired; return { ...state, hasFetched: true, keys, keysIncludingExpired, includeExpired }; }, - apiKeysMigrationStatusLoaded: (state, action): ApiKeysState => { - return { ...state, apiKeysMigrated: action.payload }; - }, setSearchQuery: (state, action): ApiKeysState => { return { ...state, searchQuery: action.payload }; }, @@ -38,8 +34,7 @@ const apiKeysSlice = createSlice({ }, }); -export const { apiKeysLoaded, includeExpiredToggled, isFetching, setSearchQuery, apiKeysMigrationStatusLoaded } = - apiKeysSlice.actions; +export const { apiKeysLoaded, includeExpiredToggled, isFetching, setSearchQuery } = apiKeysSlice.actions; export const apiKeysReducer = apiKeysSlice.reducer; diff --git a/public/app/features/api-keys/state/selectors.test.ts b/public/app/features/api-keys/state/selectors.test.ts index d96c98f083c..e1ba2f4d0f9 100644 --- a/public/app/features/api-keys/state/selectors.test.ts +++ b/public/app/features/api-keys/state/selectors.test.ts @@ -16,7 +16,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: false, - apiKeysMigrated: false, }; const keyCount = getApiKeysCount(mockState); expect(keyCount).toBe(5); @@ -29,7 +28,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: true, - apiKeysMigrated: false, }; const keyCount = getApiKeysCount(mockState); expect(keyCount).toBe(8); @@ -45,7 +43,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: false, - apiKeysMigrated: false, }; const keys = getApiKeys(mockState); expect(keys).toEqual(mockKeys); @@ -58,7 +55,6 @@ describe('API Keys selectors', () => { searchQuery: '5', hasFetched: true, includeExpired: false, - apiKeysMigrated: false, }; const keys = getApiKeys(mockState); expect(keys.length).toEqual(1); @@ -73,7 +69,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: true, - apiKeysMigrated: false, }; const keys = getApiKeys(mockState); expect(keys).toEqual(mockKeysIncludingExpired); @@ -86,7 +81,6 @@ describe('API Keys selectors', () => { searchQuery: '5', hasFetched: true, includeExpired: true, - apiKeysMigrated: false, }; const keys = getApiKeys(mockState); expect(keys.length).toEqual(1); @@ -102,7 +96,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: true, - apiKeysMigrated: false, }; const includeExpired = getIncludeExpired(mockState); expect(includeExpired).toBe(true); @@ -115,7 +108,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: false, - apiKeysMigrated: false, }; const includeExpired = getIncludeExpired(mockState); expect(includeExpired).toBe(false); @@ -130,7 +122,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: true, - apiKeysMigrated: false, }; const includeExpiredDisabled = getIncludeExpiredDisabled(mockState); expect(includeExpiredDisabled).toBe(true); @@ -143,7 +134,6 @@ describe('API Keys selectors', () => { searchQuery: '', hasFetched: true, includeExpired: false, - apiKeysMigrated: false, }; const includeExpiredDisabled = getIncludeExpired(mockState); expect(includeExpiredDisabled).toBe(false); diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx index bd459659dbe..ee941a3d043 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx @@ -23,9 +23,6 @@ const setup = (propOverrides: Partial) => { const updateServiceAccountMock = jest.fn(); const changeStateFilterMock = jest.fn(); const createServiceAccountTokenMock = jest.fn(); - const getApiKeysMigrationStatusMock = jest.fn(); - const getApiKeysMigrationInfoMock = jest.fn(); - const closeApiKeysMigrationInfoMock = jest.fn(); const props: Props = { isLoading: false, page: 0, @@ -36,8 +33,6 @@ const setup = (propOverrides: Partial) => { showPaging: false, totalPages: 1, serviceAccounts: [], - apiKeysMigrated: false, - showApiKeysMigrationInfo: false, changeQuery: changeQueryMock, fetchACOptions: fetchACOptionsMock, fetchServiceAccounts: fetchServiceAccountsMock, @@ -45,9 +40,6 @@ const setup = (propOverrides: Partial) => { updateServiceAccount: updateServiceAccountMock, changeStateFilter: changeStateFilterMock, createServiceAccountToken: createServiceAccountTokenMock, - getApiKeysMigrationStatus: getApiKeysMigrationStatusMock, - getApiKeysMigrationInfo: getApiKeysMigrationInfoMock, - closeApiKeysMigrationInfo: closeApiKeysMigrationInfoMock, }; Object.assign(props, propOverrides); diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx index 9cd3b81bcce..8f04334e591 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; -import { Alert, ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui'; +import { ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; @@ -21,9 +21,6 @@ import { updateServiceAccount, changeStateFilter, createServiceAccountToken, - getApiKeysMigrationStatus, - getApiKeysMigrationInfo, - closeApiKeysMigrationInfo, } from './state/actions'; interface OwnProps {} @@ -44,9 +41,6 @@ const mapDispatchToProps = { updateServiceAccount, changeStateFilter, createServiceAccountToken, - getApiKeysMigrationStatus, - getApiKeysMigrationInfo, - closeApiKeysMigrationInfo, }; const connector = connect(mapStateToProps, mapDispatchToProps); @@ -57,8 +51,6 @@ export const ServiceAccountsListPageUnconnected = ({ roleOptions, query, serviceAccountStateFilter, - apiKeysMigrated, - showApiKeysMigrationInfo, changeQuery, fetchACOptions, fetchServiceAccounts, @@ -66,9 +58,6 @@ export const ServiceAccountsListPageUnconnected = ({ updateServiceAccount, changeStateFilter, createServiceAccountToken, - getApiKeysMigrationStatus, - getApiKeysMigrationInfo, - closeApiKeysMigrationInfo, }: Props): JSX.Element => { const styles = useStyles2(getStyles); const [isAddModalOpen, setIsAddModalOpen] = useState(false); @@ -79,12 +68,10 @@ export const ServiceAccountsListPageUnconnected = ({ useEffect(() => { fetchServiceAccounts({ withLoadingIndicator: true }); - getApiKeysMigrationStatus(); - getApiKeysMigrationInfo(); if (contextSrv.licensedAccessControlEnabled()) { fetchACOptions(); } - }, [fetchACOptions, fetchServiceAccounts, getApiKeysMigrationStatus, getApiKeysMigrationInfo]); + }, [fetchACOptions, fetchServiceAccounts]); const noServiceAccountsCreated = serviceAccounts.length === 0 && serviceAccountStateFilter === ServiceAccountStateFilter.All && !query; @@ -160,10 +147,6 @@ export const ServiceAccountsListPageUnconnected = ({ setCurrentServiceAccount(null); }; - const onMigrationInfoClose = () => { - closeApiKeysMigrationInfo(); - }; - const docsLink = ( - {apiKeysMigrated && showApiKeysMigrationInfo && ( - - )}

Service accounts

diff --git a/public/app/features/serviceaccounts/constants.ts b/public/app/features/serviceaccounts/constants.ts deleted file mode 100644 index 40ed3e20666..00000000000 --- a/public/app/features/serviceaccounts/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const API_KEYS_MIGRATION_INFO_STORAGE_KEY = 'grafana.serviceaccounts.showApiKeysMigrationInfo'; diff --git a/public/app/features/serviceaccounts/state/actions.ts b/public/app/features/serviceaccounts/state/actions.ts index 74e280f0c5e..7409beccba5 100644 --- a/public/app/features/serviceaccounts/state/actions.ts +++ b/public/app/features/serviceaccounts/state/actions.ts @@ -3,11 +3,9 @@ import { debounce } from 'lodash'; import { getBackendSrv } from '@grafana/runtime'; import { fetchRoleOptions } from 'app/core/components/RolePicker/api'; import { contextSrv } from 'app/core/services/context_srv'; -import store from 'app/core/store'; import { AccessControlAction, ServiceAccountDTO, ServiceAccountStateFilter, ThunkResult } from 'app/types'; import { ServiceAccountToken } from '../components/CreateTokenModal'; -import { API_KEYS_MIGRATION_INFO_STORAGE_KEY } from '../constants'; import { acOptionsLoaded, @@ -16,9 +14,7 @@ import { serviceAccountsFetchBegin, serviceAccountsFetched, serviceAccountsFetchEnd, - apiKeysMigrationStatusLoaded, stateFilterChanged, - showApiKeysMigrationInfoLoaded, } from './reducers'; const BASE_URL = `/api/serviceaccounts`; @@ -36,15 +32,6 @@ export function fetchACOptions(): ThunkResult { }; } -export function getApiKeysMigrationStatus(): ThunkResult { - return async (dispatch) => { - if (contextSrv.hasPermission(AccessControlAction.ServiceAccountsRead)) { - const result = await getBackendSrv().get('/api/serviceaccounts/migrationstatus'); - dispatch(apiKeysMigrationStatusLoaded(!!result?.migrated)); - } - }; -} - interface FetchServiceAccountsParams { withLoadingIndicator: boolean; } @@ -138,17 +125,3 @@ export function changePage(page: number): ThunkResult { dispatch(fetchServiceAccounts()); }; } - -export function getApiKeysMigrationInfo(): ThunkResult { - return async (dispatch) => { - const showApiKeysMigrationInfo = store.getBool(API_KEYS_MIGRATION_INFO_STORAGE_KEY, false); - dispatch(showApiKeysMigrationInfoLoaded(showApiKeysMigrationInfo)); - }; -} - -export function closeApiKeysMigrationInfo(): ThunkResult { - return async (dispatch) => { - store.set(API_KEYS_MIGRATION_INFO_STORAGE_KEY, false); - dispatch(getApiKeysMigrationInfo()); - }; -} diff --git a/public/app/features/serviceaccounts/state/reducers.ts b/public/app/features/serviceaccounts/state/reducers.ts index b87855e1f0c..8ff78f79ae4 100644 --- a/public/app/features/serviceaccounts/state/reducers.ts +++ b/public/app/features/serviceaccounts/state/reducers.ts @@ -50,8 +50,6 @@ export const initialStateList: ServiceAccountsState = { totalPages: 1, showPaging: false, serviceAccountStateFilter: ServiceAccountStateFilter.All, - apiKeysMigrated: false, - showApiKeysMigrationInfo: false, }; interface ServiceAccountsFetched { @@ -87,12 +85,6 @@ const serviceAccountsSlice = createSlice({ acOptionsLoaded: (state, action: PayloadAction): ServiceAccountsState => { return { ...state, roleOptions: action.payload }; }, - apiKeysMigrationStatusLoaded: (state, action): ServiceAccountsState => { - return { ...state, apiKeysMigrated: action.payload }; - }, - showApiKeysMigrationInfoLoaded: (state, action): ServiceAccountsState => { - return { ...state, showApiKeysMigrationInfo: action.payload }; - }, queryChanged: (state, action: PayloadAction) => { return { ...state, @@ -117,8 +109,6 @@ export const { serviceAccountsFetchEnd, serviceAccountsFetched, acOptionsLoaded, - apiKeysMigrationStatusLoaded, - showApiKeysMigrationInfoLoaded, pageChanged, stateFilterChanged, queryChanged, diff --git a/public/app/types/apiKeys.ts b/public/app/types/apiKeys.ts index c8c06f4a0db..241986acf15 100644 --- a/public/app/types/apiKeys.ts +++ b/public/app/types/apiKeys.ts @@ -15,17 +15,10 @@ export interface ApiKey extends WithAccessControlMetadata { lastUsedAt?: string; } -export interface NewApiKey { - name: string; - role: OrgRole; - secondsToLive: string; -} - export interface ApiKeysState { includeExpired: boolean; keys: ApiKey[]; keysIncludingExpired: ApiKey[]; searchQuery: string; hasFetched: boolean; - apiKeysMigrated: boolean; } diff --git a/public/app/types/serviceaccount.ts b/public/app/types/serviceaccount.ts index 9fc051ceca6..cad4fea10d9 100644 --- a/public/app/types/serviceaccount.ts +++ b/public/app/types/serviceaccount.ts @@ -65,8 +65,6 @@ export interface ServiceAccountsState { serviceAccounts: ServiceAccountDTO[]; isLoading: boolean; roleOptions: Role[]; - apiKeysMigrated: boolean; - showApiKeysMigrationInfo: boolean; // search / filtering query: string;