mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 04:01:49 +08:00
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
This commit is contained in:
@ -19,6 +19,8 @@ func (s *QueryHistoryService) registerAPIEndpoints() {
|
|||||||
entities.Post("/star/:uid", middleware.ReqSignedIn, routing.Wrap(s.starHandler))
|
entities.Post("/star/:uid", middleware.ReqSignedIn, routing.Wrap(s.starHandler))
|
||||||
entities.Delete("/star/:uid", middleware.ReqSignedIn, routing.Wrap(s.unstarHandler))
|
entities.Delete("/star/:uid", middleware.ReqSignedIn, routing.Wrap(s.unstarHandler))
|
||||||
entities.Patch("/:uid", middleware.ReqSignedIn, routing.Wrap(s.patchCommentHandler))
|
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})
|
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})
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ package queryhistory
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -265,3 +266,61 @@ func (s QueryHistoryService) unstarQuery(ctx context.Context, user *models.Signe
|
|||||||
|
|
||||||
return dto, nil
|
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
|
||||||
|
}
|
||||||
|
@ -78,3 +78,21 @@ type DeleteQueryFromQueryHistoryResponse struct {
|
|||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Message string `json:"message"`
|
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"`
|
||||||
|
}
|
||||||
|
@ -33,6 +33,7 @@ type Service interface {
|
|||||||
PatchQueryCommentInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string, cmd PatchQueryCommentInQueryHistoryCommand) (QueryHistoryDTO, error)
|
PatchQueryCommentInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string, cmd PatchQueryCommentInQueryHistoryCommand) (QueryHistoryDTO, error)
|
||||||
StarQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string) (QueryHistoryDTO, error)
|
StarQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string) (QueryHistoryDTO, error)
|
||||||
UnstarQueryInQueryHistory(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 {
|
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) {
|
func (s QueryHistoryService) UnstarQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, UID string) (QueryHistoryDTO, error) {
|
||||||
return s.unstarQuery(ctx, user, UID)
|
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)
|
||||||
|
}
|
||||||
|
120
pkg/services/queryhistory/queryhistory_migrate_test.go
Normal file
120
pkg/services/queryhistory/queryhistory_migrate_test.go
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user