mirror of https://github.com/grafana/grafana
Query history: Create API to add query to query history (#44479)
* Create config to enable/disable query history * Create add to query history functionality * Add documentation * Add test * Refactor * Add test * Fix built errors and linting errors * Refactor * Remove old tests * Refactor, adjust based on feedback, add new test * Update default valuepull/44613/head
parent
ca24b95b49
commit
4e37a53a1c
@ -0,0 +1,59 @@ |
||||
+++ |
||||
title = "Query History HTTP API " |
||||
description = "Grafana Query History HTTP API" |
||||
keywords = ["grafana", "http", "documentation", "api", "queryHistory"] |
||||
aliases = ["/docs/grafana/latest/http_api/query_history/"] |
||||
+++ |
||||
|
||||
# Query history API |
||||
|
||||
This API can be used to add queries to Query history. It requires that the user is logged in and that Query history feature is enabled in config file. |
||||
|
||||
## Add query to Query history |
||||
|
||||
`POST /api/query-history` |
||||
|
||||
Adds query to query history. |
||||
|
||||
**Example request:** |
||||
|
||||
```http |
||||
POST /api/query-history HTTP/1.1 |
||||
Accept: application/json |
||||
Content-Type: application/json |
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk |
||||
{ |
||||
"dataSourceUid": "PE1C5CBDA0504A6A3", |
||||
"queries": [ |
||||
{ |
||||
"refId": "A", |
||||
"key": "Q-87fed8e3-62ba-4eb2-8d2a-4129979bb4de-0", |
||||
"scenarioId": "csv_content", |
||||
"datasource": { |
||||
"type": "testdata", |
||||
"uid": "PD8C576611E62080A" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
JSON body schema: |
||||
|
||||
- **datasourceUid** – Data source uid. |
||||
- **queries** – JSON of query or queries. |
||||
|
||||
**Example response:** |
||||
|
||||
```http |
||||
HTTP/1.1 200 |
||||
Content-Type: application/json |
||||
{ |
||||
"message": "Query successfully added to query history", |
||||
} |
||||
``` |
||||
|
||||
Status codes: |
||||
|
||||
- **200** – OK |
||||
- **500** – Errors (invalid JSON, missing or invalid fields) |
||||
@ -0,0 +1,31 @@ |
||||
package queryhistory |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/grafana/grafana/pkg/api/response" |
||||
"github.com/grafana/grafana/pkg/api/routing" |
||||
"github.com/grafana/grafana/pkg/middleware" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/web" |
||||
) |
||||
|
||||
func (s *QueryHistoryService) registerAPIEndpoints() { |
||||
s.RouteRegister.Group("/api/query-history", func(entities routing.RouteRegister) { |
||||
entities.Post("/", middleware.ReqSignedIn, routing.Wrap(s.createHandler)) |
||||
}) |
||||
} |
||||
|
||||
func (s *QueryHistoryService) createHandler(c *models.ReqContext) response.Response { |
||||
cmd := CreateQueryInQueryHistoryCommand{} |
||||
if err := web.Bind(c.Req, &cmd); err != nil { |
||||
return response.Error(http.StatusBadRequest, "bad request data", err) |
||||
} |
||||
|
||||
err := s.CreateQueryInQueryHistory(c.Req.Context(), c.SignedInUser, cmd) |
||||
if err != nil { |
||||
return response.Error(500, "Failed to create query history", err) |
||||
} |
||||
|
||||
return response.Success("Query successfully added to query history") |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
package queryhistory |
||||
|
||||
import ( |
||||
"context" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
func (s QueryHistoryService) createQuery(ctx context.Context, user *models.SignedInUser, cmd CreateQueryInQueryHistoryCommand) error { |
||||
queryHistory := QueryHistory{ |
||||
OrgId: user.OrgId, |
||||
Uid: util.GenerateShortUID(), |
||||
Queries: cmd.Queries, |
||||
DatasourceUid: cmd.DatasourceUid, |
||||
CreatedBy: user.UserId, |
||||
CreatedAt: time.Now().Unix(), |
||||
Comment: "", |
||||
} |
||||
|
||||
err := s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error { |
||||
_, err := session.Insert(&queryHistory) |
||||
return err |
||||
}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
package queryhistory |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
) |
||||
|
||||
type QueryHistory struct { |
||||
Id int64 `json:"id"` |
||||
Uid string `json:"uid"` |
||||
DatasourceUid string `json:"datasourceUid"` |
||||
OrgId int64 `json:"orgId"` |
||||
CreatedBy int64 `json:"createdBy"` |
||||
CreatedAt int64 `json:"createdAt"` |
||||
Comment string `json:"comment"` |
||||
Queries *simplejson.Json `json:"queries"` |
||||
} |
||||
|
||||
type CreateQueryInQueryHistoryCommand struct { |
||||
DatasourceUid string `json:"datasourceUid"` |
||||
Queries *simplejson.Json `json:"queries"` |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
package queryhistory |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister) *QueryHistoryService { |
||||
s := &QueryHistoryService{ |
||||
SQLStore: sqlStore, |
||||
Cfg: cfg, |
||||
RouteRegister: routeRegister, |
||||
log: log.New("query-history"), |
||||
} |
||||
|
||||
// Register routes only when query history is enabled
|
||||
if s.Cfg.QueryHistoryEnabled { |
||||
s.registerAPIEndpoints() |
||||
} |
||||
|
||||
return s |
||||
} |
||||
|
||||
type Service interface { |
||||
CreateQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, cmd CreateQueryInQueryHistoryCommand) error |
||||
} |
||||
|
||||
type QueryHistoryService struct { |
||||
SQLStore *sqlstore.SQLStore |
||||
Cfg *setting.Cfg |
||||
RouteRegister routing.RouteRegister |
||||
log log.Logger |
||||
} |
||||
|
||||
func (s QueryHistoryService) CreateQueryInQueryHistory(ctx context.Context, user *models.SignedInUser, cmd CreateQueryInQueryHistoryCommand) error { |
||||
return s.createQuery(ctx, user, cmd) |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
package queryhistory |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestCreateQueryInQueryHistory(t *testing.T) { |
||||
testScenario(t, "When users tries to create query in query history it should succeed", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
command := CreateQueryInQueryHistoryCommand{ |
||||
DatasourceUid: "NCzh67i", |
||||
Queries: simplejson.NewFromAny(map[string]interface{}{ |
||||
"expr": "test", |
||||
}), |
||||
} |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
require.Equal(t, 200, resp.Status()) |
||||
}) |
||||
} |
||||
@ -0,0 +1,77 @@ |
||||
package queryhistory |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/json" |
||||
"io" |
||||
"net/http" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/grafana/grafana/pkg/web" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
var ( |
||||
testOrgID = int64(1) |
||||
testUserID = int64(1) |
||||
) |
||||
|
||||
type scenarioContext struct { |
||||
ctx *web.Context |
||||
service *QueryHistoryService |
||||
reqContext *models.ReqContext |
||||
sqlStore *sqlstore.SQLStore |
||||
} |
||||
|
||||
func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) { |
||||
t.Helper() |
||||
|
||||
t.Run(desc, func(t *testing.T) { |
||||
ctx := web.Context{Req: &http.Request{}} |
||||
sqlStore := sqlstore.InitTestDB(t) |
||||
service := QueryHistoryService{ |
||||
Cfg: setting.NewCfg(), |
||||
SQLStore: sqlStore, |
||||
} |
||||
|
||||
service.Cfg.QueryHistoryEnabled = true |
||||
|
||||
user := models.SignedInUser{ |
||||
UserId: testUserID, |
||||
Name: "Signed In User", |
||||
Login: "signed_in_user", |
||||
Email: "signed.in.user@test.com", |
||||
OrgId: testOrgID, |
||||
OrgRole: models.ROLE_VIEWER, |
||||
LastSeenAt: time.Now(), |
||||
} |
||||
|
||||
_, err := sqlStore.CreateUser(context.Background(), models.CreateUserCommand{ |
||||
Email: "signed.in.user@test.com", |
||||
Name: "Signed In User", |
||||
Login: "signed_in_user", |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
sc := scenarioContext{ |
||||
ctx: &ctx, |
||||
service: &service, |
||||
sqlStore: sqlStore, |
||||
reqContext: &models.ReqContext{ |
||||
Context: &ctx, |
||||
SignedInUser: &user, |
||||
}, |
||||
} |
||||
fn(t, sc) |
||||
}) |
||||
} |
||||
|
||||
func mockRequestBody(v interface{}) io.ReadCloser { |
||||
b, _ := json.Marshal(v) |
||||
return io.NopCloser(bytes.NewReader(b)) |
||||
} |
||||
@ -0,0 +1,28 @@ |
||||
package migrations |
||||
|
||||
import ( |
||||
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator" |
||||
) |
||||
|
||||
func addQueryHistoryMigrations(mg *Migrator) { |
||||
queryHistoryV1 := Table{ |
||||
Name: "query_history", |
||||
Columns: []*Column{ |
||||
{Name: "id", Type: DB_BigInt, Nullable: false, IsPrimaryKey: true, IsAutoIncrement: true}, |
||||
{Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: false}, |
||||
{Name: "org_id", Type: DB_BigInt, Nullable: false}, |
||||
{Name: "datasource_uid", Type: DB_NVarchar, Length: 40, Nullable: false}, |
||||
{Name: "created_by", Type: DB_Int, Nullable: false}, |
||||
{Name: "created_at", Type: DB_Int, Nullable: false}, |
||||
{Name: "comment", Type: DB_Text, Nullable: false}, |
||||
{Name: "queries", Type: DB_Text, Nullable: false}, |
||||
}, |
||||
Indices: []*Index{ |
||||
{Cols: []string{"org_id", "created_by", "datasource_uid"}}, |
||||
}, |
||||
} |
||||
|
||||
mg.AddMigration("create query_history table v1", NewAddTableMigration(queryHistoryV1)) |
||||
|
||||
mg.AddMigration("add index query_history.org_id-created_by-datasource_uid", NewAddIndexMigration(queryHistoryV1, queryHistoryV1.Indices[0])) |
||||
} |
||||
Loading…
Reference in new issue