From aceedb3a32b00d88d16c7542c0828ad97bc389c6 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 14 Apr 2022 09:33:41 +0200 Subject: [PATCH] Query history: Add migration endpoint (#47551) * Add endpoint for migration * Check for createdAt * Query history: Remove returning of dtos * Query history: Fix CreatedAt * Refactor based on suggestions * Insert into table in batches --- pkg/services/queryhistory/api.go | 16 +++ pkg/services/queryhistory/database.go | 59 +++++++++ pkg/services/queryhistory/models.go | 18 +++ pkg/services/queryhistory/queryhistory.go | 5 + .../queryhistory/queryhistory_migrate_test.go | 120 ++++++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 pkg/services/queryhistory/queryhistory_migrate_test.go diff --git a/pkg/services/queryhistory/api.go b/pkg/services/queryhistory/api.go index dc2245407f4..fd719cf7b9e 100644 --- a/pkg/services/queryhistory/api.go +++ b/pkg/services/queryhistory/api.go @@ -19,6 +19,8 @@ func (s *QueryHistoryService) registerAPIEndpoints() { entities.Post("/star/:uid", middleware.ReqSignedIn, routing.Wrap(s.starHandler)) entities.Delete("/star/:uid", middleware.ReqSignedIn, routing.Wrap(s.unstarHandler)) entities.Patch("/:uid", middleware.ReqSignedIn, routing.Wrap(s.patchCommentHandler)) + // Remove migrate endpoint in Grafana v10 as breaking change + entities.Post("/migrate", middleware.ReqSignedIn, routing.Wrap(s.migrateHandler)) }) } @@ -117,3 +119,17 @@ func (s *QueryHistoryService) unstarHandler(c *models.ReqContext) response.Respo return response.JSON(http.StatusOK, QueryHistoryResponse{Result: query}) } + +func (s *QueryHistoryService) migrateHandler(c *models.ReqContext) response.Response { + cmd := MigrateQueriesToQueryHistoryCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } + + totalCount, starredCount, err := s.MigrateQueriesToQueryHistory(c.Req.Context(), c.SignedInUser, cmd) + if err != nil { + return response.Error(http.StatusInternalServerError, "Failed to migrate query history", err) + } + + return response.JSON(http.StatusOK, QueryHistoryMigrationResponse{Message: "Query history successfully migrated", TotalCount: totalCount, StarredCount: starredCount}) +} diff --git a/pkg/services/queryhistory/database.go b/pkg/services/queryhistory/database.go index edb16de741f..3f718a6b69a 100644 --- a/pkg/services/queryhistory/database.go +++ b/pkg/services/queryhistory/database.go @@ -3,6 +3,7 @@ package queryhistory import ( "context" "errors" + "fmt" "time" "github.com/grafana/grafana/pkg/models" @@ -265,3 +266,61 @@ func (s QueryHistoryService) unstarQuery(ctx context.Context, user *models.Signe return dto, nil } + +func (s QueryHistoryService) migrateQueries(ctx context.Context, user *models.SignedInUser, cmd MigrateQueriesToQueryHistoryCommand) (int, int, error) { + queryHistories := make([]*QueryHistory, 0, len(cmd.Queries)) + starredQueries := make([]*QueryHistoryStar, 0) + + err := s.SQLStore.WithTransactionalDbSession(ctx, func(session *sqlstore.DBSession) error { + for _, query := range cmd.Queries { + uid := util.GenerateShortUID() + queryHistories = append(queryHistories, &QueryHistory{ + OrgID: user.OrgId, + UID: uid, + Queries: query.Queries, + DatasourceUID: query.DatasourceUID, + CreatedBy: user.UserId, + CreatedAt: query.CreatedAt, + Comment: query.Comment, + }) + + if query.Starred { + starredQueries = append(starredQueries, &QueryHistoryStar{ + UserID: user.UserId, + QueryUID: uid, + }) + } + } + + batchSize := 50 + var err error + for i := 0; i < len(queryHistories); i += batchSize { + j := i + batchSize + if j > len(queryHistories) { + j = len(queryHistories) + } + _, err = session.InsertMulti(queryHistories[i:j]) + if err != nil { + return err + } + } + + for i := 0; i < len(starredQueries); i += batchSize { + j := i + batchSize + if j > len(starredQueries) { + j = len(starredQueries) + } + _, err = session.InsertMulti(starredQueries[i:j]) + if err != nil { + return err + } + } + return err + }) + + if err != nil { + return 0, 0, fmt.Errorf("failed to migrate query history: %w", err) + } + + return len(queryHistories), len(starredQueries), nil +} diff --git a/pkg/services/queryhistory/models.go b/pkg/services/queryhistory/models.go index 7dfc9de69a0..19d7d77ad5c 100644 --- a/pkg/services/queryhistory/models.go +++ b/pkg/services/queryhistory/models.go @@ -78,3 +78,21 @@ type DeleteQueryFromQueryHistoryResponse struct { ID int64 `json:"id"` Message string `json:"message"` } + +type MigrateQueriesToQueryHistoryCommand struct { + Queries []QueryToMigrate `json:"queries"` +} + +type QueryToMigrate struct { + DatasourceUID string `json:"datasourceUid"` + Queries *simplejson.Json `json:"queries"` + CreatedAt int64 `json:"createdAt"` + Comment string `json:"comment"` + Starred bool `json:"starred"` +} + +type QueryHistoryMigrationResponse struct { + Message string `json:"message"` + TotalCount int `json:"totalCount"` + StarredCount int `json:"starredCount"` +} diff --git a/pkg/services/queryhistory/queryhistory.go b/pkg/services/queryhistory/queryhistory.go index bd8c88ab826..c3c1290cb00 100644 --- a/pkg/services/queryhistory/queryhistory.go +++ b/pkg/services/queryhistory/queryhistory.go @@ -33,6 +33,7 @@ type Service interface { PatchQueryCommentInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string, cmd PatchQueryCommentInQueryHistoryCommand) (QueryHistoryDTO, error) StarQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string) (QueryHistoryDTO, error) UnstarQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string) (QueryHistoryDTO, error) + MigrateQueriesToQueryHistory(ctx context.Context, user *models.SignedInUser, cmd MigrateQueriesToQueryHistoryCommand) (int, int, error) } type QueryHistoryService struct { @@ -65,3 +66,7 @@ func (s QueryHistoryService) StarQueryInQueryHistory(ctx context.Context, user * func (s QueryHistoryService) UnstarQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string) (QueryHistoryDTO, error) { return s.unstarQuery(ctx, user, UID) } + +func (s QueryHistoryService) MigrateQueriesToQueryHistory(ctx context.Context, user *models.SignedInUser, cmd MigrateQueriesToQueryHistoryCommand) (int, int, error) { + return s.migrateQueries(ctx, user, cmd) +} diff --git a/pkg/services/queryhistory/queryhistory_migrate_test.go b/pkg/services/queryhistory/queryhistory_migrate_test.go new file mode 100644 index 00000000000..eb896dc9b33 --- /dev/null +++ b/pkg/services/queryhistory/queryhistory_migrate_test.go @@ -0,0 +1,120 @@ +//go:build integration +// +build integration + +package queryhistory + +import ( + "encoding/json" + "testing" + "time" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/stretchr/testify/require" +) + +func TestMigrateQueriesToQueryHistory(t *testing.T) { + testScenario(t, "When users tries to migrate 1 query in query history it should succeed", + func(t *testing.T, sc scenarioContext) { + command := MigrateQueriesToQueryHistoryCommand{ + Queries: []QueryToMigrate{ + { + DatasourceUID: "NCzh67i", + Queries: simplejson.NewFromAny(map[string]interface{}{ + "expr": "test", + }), + Comment: "", + Starred: false, + CreatedAt: time.Now().Unix(), + }, + }, + } + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.migrateHandler(sc.reqContext) + var response QueryHistoryMigrationResponse + err := json.Unmarshal(resp.Body(), &response) + require.NoError(t, err) + require.Equal(t, 200, resp.Status()) + require.Equal(t, "Query history successfully migrated", response.Message) + require.Equal(t, 1, response.TotalCount) + require.Equal(t, 0, response.StarredCount) + }) + + testScenario(t, "When users tries to migrate multiple queries in query history it should succeed", + func(t *testing.T, sc scenarioContext) { + command := MigrateQueriesToQueryHistoryCommand{ + Queries: []QueryToMigrate{ + { + DatasourceUID: "NCzh67i", + Queries: simplejson.NewFromAny(map[string]interface{}{ + "expr": "test1", + }), + Comment: "", + Starred: false, + CreatedAt: time.Now().Unix(), + }, + { + DatasourceUID: "NCzh67i", + Queries: simplejson.NewFromAny(map[string]interface{}{ + "expr": "test2", + }), + Comment: "", + Starred: false, + CreatedAt: time.Now().Unix() - int64(100), + }, + { + DatasourceUID: "ABch68f", + Queries: simplejson.NewFromAny(map[string]interface{}{ + "expr": "test3", + }), + Comment: "", + Starred: false, + CreatedAt: time.Now().Unix() - int64(1000), + }, + }, + } + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.migrateHandler(sc.reqContext) + var response QueryHistoryMigrationResponse + err := json.Unmarshal(resp.Body(), &response) + require.NoError(t, err) + require.Equal(t, 200, resp.Status()) + require.Equal(t, "Query history successfully migrated", response.Message) + require.Equal(t, 3, response.TotalCount) + require.Equal(t, 0, response.StarredCount) + }) + + testScenario(t, "When users tries to migrate starred and not starred query in query history it should succeed", + func(t *testing.T, sc scenarioContext) { + command := MigrateQueriesToQueryHistoryCommand{ + Queries: []QueryToMigrate{ + { + DatasourceUID: "NCzh67i", + Queries: simplejson.NewFromAny(map[string]interface{}{ + "expr": "test1", + }), + Comment: "", + Starred: true, + CreatedAt: time.Now().Unix(), + }, + { + DatasourceUID: "NCzh67i", + Queries: simplejson.NewFromAny(map[string]interface{}{ + "expr": "test2", + }), + Comment: "", + Starred: false, + CreatedAt: time.Now().Unix() - int64(100), + }, + }, + } + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.migrateHandler(sc.reqContext) + var response QueryHistoryMigrationResponse + err := json.Unmarshal(resp.Body(), &response) + require.NoError(t, err) + require.Equal(t, 200, resp.Status()) + require.Equal(t, "Query history successfully migrated", response.Message) + require.Equal(t, 2, response.TotalCount) + require.Equal(t, 1, response.StarredCount) + }) +}