Query history: Add from and to parameters (#48212)

* Query history: Add from and to parameters

* Update docs/sources/http_api/query_history.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Update docs/sources/http_api/query_history.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Update docs/sources/http_api/query_history.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Implement Grafana relative time range

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
Ivana Huckova
2022-04-29 09:55:33 +02:00
committed by GitHub
parent 17eca4505c
commit b92fe0e0f5
6 changed files with 175 additions and 16 deletions

View File

@@ -85,12 +85,13 @@ Returns a list of queries in the query history that matches the search criteria.
## Delete query from Query history by UID ## Delete query from Query history by UID
`DELETE /api/query-history/:uid` `DELETE /api/query-history/:uid`
Deletes the query in query history that matches the specified uid. It requires that the user is logged in and that Query history feature is enabled in config file.
Deletes the query in query history that matches the specified uid. It requires that the user is logged in and that Query history feature is enabled in config file. Deletes the query in query history that matches the specified uid. It requires that the user is logged in and that Query history feature is enabled in config file.
**Example Request**: **Example Request**:
```http ```http
DELETE /api/query-history/P8zM2I1nz HTTP/1.1
Accept: application/json Accept: application/json
Content-Type: application/json Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk

View File

@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb/legacydata"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@@ -39,6 +40,8 @@ func (s *QueryHistoryService) createHandler(c *models.ReqContext) response.Respo
} }
func (s *QueryHistoryService) searchHandler(c *models.ReqContext) response.Response { func (s *QueryHistoryService) searchHandler(c *models.ReqContext) response.Response {
timeRange := legacydata.NewDataTimeRange(c.Query("from"), c.Query("to"))
query := SearchInQueryHistoryQuery{ query := SearchInQueryHistoryQuery{
DatasourceUIDs: c.QueryStrings("datasourceUid"), DatasourceUIDs: c.QueryStrings("datasourceUid"),
SearchString: c.Query("searchString"), SearchString: c.Query("searchString"),
@@ -46,6 +49,8 @@ func (s *QueryHistoryService) searchHandler(c *models.ReqContext) response.Respo
Sort: c.Query("sort"), Sort: c.Query("sort"),
Page: c.QueryInt("page"), Page: c.QueryInt("page"),
Limit: c.QueryInt("limit"), Limit: c.QueryInt("limit"),
From: timeRange.GetFromAsSecondsEpoch(),
To: timeRange.GetToAsSecondsEpoch(),
} }
result, err := s.SearchInQueryHistory(c.Req.Context(), c.SignedInUser, query) result, err := s.SearchInQueryHistory(c.Req.Context(), c.SignedInUser, query)

View File

@@ -2,7 +2,6 @@ package queryhistory
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
@@ -47,8 +46,8 @@ func (s QueryHistoryService) searchQueries(ctx context.Context, user *models.Sig
var dtos []QueryHistoryDTO var dtos []QueryHistoryDTO
var allQueries []interface{} var allQueries []interface{}
if len(query.DatasourceUIDs) == 0 { if query.To <= 0 {
return QueryHistorySearchResult{}, errors.New("no selected data source for query history search") query.To = time.Now().Unix()
} }
if query.Page <= 0 { if query.Page <= 0 {

View File

@@ -41,6 +41,8 @@ type SearchInQueryHistoryQuery struct {
Sort string `json:"sort"` Sort string `json:"sort"`
Page int `json:"page"` Page int `json:"page"`
Limit int `json:"limit"` Limit int `json:"limit"`
From int64 `json:"from"`
To int64 `json:"to"`
} }
type PatchQueryCommentInQueryHistoryCommand struct { type PatchQueryCommentInQueryHistoryCommand struct {

View File

@@ -5,18 +5,14 @@ package queryhistory
import ( import (
"encoding/json" "encoding/json"
"strconv"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestGetQueriesFromQueryHistory(t *testing.T) { func TestGetQueriesFromQueryHistory(t *testing.T) {
testScenario(t, "When users tries to get query without datasourceUid, it should fail",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.searchHandler(sc.reqContext)
require.Equal(t, 500, resp.Status())
})
testScenario(t, "When users tries to get query in empty query history, it should return empty result", testScenario(t, "When users tries to get query in empty query history, it should return empty result",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", "test") sc.reqContext.Req.Form.Add("datasourceUid", "test")
@@ -39,6 +35,16 @@ func TestGetQueriesFromQueryHistory(t *testing.T) {
require.Equal(t, 1, response.Result.TotalCount) require.Equal(t, 1, response.Result.TotalCount)
}) })
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries without datasourceUid, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 3, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries with datasourceUid, it should return correct queries", testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries with datasourceUid, it should return correct queries",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", sc.initialResult.Result.DatasourceUID) sc.reqContext.Req.Form.Add("datasourceUid", sc.initialResult.Result.DatasourceUID)
@@ -77,7 +83,7 @@ func TestGetQueriesFromQueryHistory(t *testing.T) {
require.Equal(t, 0, response.Result.TotalCount) require.Equal(t, 0, response.Result.TotalCount)
}) })
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries with multiple datasourceUid, it should return correct queries", testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries with multiple datasourceUid, it should return correct queries",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", testDsUID1) sc.reqContext.Req.Form.Add("datasourceUid", testDsUID1)
sc.reqContext.Req.Form.Add("datasourceUid", testDsUID2) sc.reqContext.Req.Form.Add("datasourceUid", testDsUID2)
@@ -114,4 +120,146 @@ func TestGetQueriesFromQueryHistory(t *testing.T) {
require.Equal(t, 2, response.Result.TotalCount) require.Equal(t, 2, response.Result.TotalCount)
require.Equal(t, true, response.Result.QueryHistory[0].Starred) require.Equal(t, true, response.Result.QueryHistory[0].Starred)
}) })
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from filter, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("from", strconv.FormatInt(time.Now().UnixMilli()-60*1000, 10))
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 3, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using relative from filter, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("from", "now-1m")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 3, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from filter, it should return no queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("from", strconv.FormatInt(time.Now().UnixMilli()+60*1000, 10))
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 0, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from filter, it should return no queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("from", "now+1m")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 0, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using to filter, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", strconv.FormatInt(time.Now().UnixMilli(), 10))
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 3, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using to filter, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", "now")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 3, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using to filter, it should return no queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", strconv.FormatInt(time.Now().UnixMilli()-60*1000, 10))
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 0, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using to filter, it should return no queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", "now-1m")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 0, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from and to filter, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", strconv.FormatInt(time.Now().UnixMilli(), 10))
sc.reqContext.Req.Form.Add("from", strconv.FormatInt(time.Now().UnixMilli()-60*1000, 10))
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 3, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from and to filter with other filters, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", strconv.FormatInt(time.Now().UnixMilli(), 10))
sc.reqContext.Req.Form.Add("from", strconv.FormatInt(time.Now().UnixMilli()-60*1000, 10))
sc.reqContext.Req.Form.Add("datasourceUid", testDsUID1)
sc.reqContext.Req.Form.Add("searchString", "2")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 2, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from and to filter with other filters, it should return correct queries",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", "now")
sc.reqContext.Req.Form.Add("from", "now-1m")
sc.reqContext.Req.Form.Add("datasourceUid", testDsUID1)
sc.reqContext.Req.Form.Add("searchString", "2")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 2, response.Result.TotalCount)
})
testScenarioWithMultipleQueriesInQueryHistory(t, "When users tries to get queries using from and to filter with other filters, it should return no query",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("to", strconv.FormatInt(time.Now().UnixMilli()-60, 10))
sc.reqContext.Req.Form.Add("from", strconv.FormatInt(time.Now().UnixMilli()+60, 10))
sc.reqContext.Req.Form.Add("datasourceUid", testDsUID1)
sc.reqContext.Req.Form.Add("searchString", "2")
resp := sc.service.searchHandler(sc.reqContext)
var response QueryHistorySearchResponse
err := json.Unmarshal(resp.Body(), &response)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 0, response.Result.TotalCount)
})
} }

View File

@@ -23,12 +23,16 @@ func writeStarredSQL(query SearchInQueryHistoryQuery, sqlStore *sqlstore.SQLStor
} }
func writeFiltersSQL(query SearchInQueryHistoryQuery, user *models.SignedInUser, sqlStore *sqlstore.SQLStore, builder *sqlstore.SQLBuilder) { func writeFiltersSQL(query SearchInQueryHistoryQuery, user *models.SignedInUser, sqlStore *sqlstore.SQLStore, builder *sqlstore.SQLBuilder) {
params := []interface{}{user.OrgId, user.UserId, "%" + query.SearchString + "%", "%" + query.SearchString + "%"} params := []interface{}{user.OrgId, user.UserId, query.From, query.To, "%" + query.SearchString + "%", "%" + query.SearchString + "%"}
for _, uid := range query.DatasourceUIDs {
params = append(params, uid)
}
var sql bytes.Buffer var sql bytes.Buffer
sql.WriteString(" WHERE query_history.org_id = ? AND query_history.created_by = ? AND (query_history.queries " + sqlStore.Dialect.LikeStr() + " ? OR query_history.comment " + sqlStore.Dialect.LikeStr() + " ?) AND query_history.datasource_uid IN (? " + strings.Repeat(",?", len(query.DatasourceUIDs)-1) + ") ") sql.WriteString(" WHERE query_history.org_id = ? AND query_history.created_by = ? AND query_history.created_at >= ? AND query_history.created_at <= ? AND (query_history.queries " + sqlStore.Dialect.LikeStr() + " ? OR query_history.comment " + sqlStore.Dialect.LikeStr() + " ?) ")
if len(query.DatasourceUIDs) > 0 {
for _, uid := range query.DatasourceUIDs {
params = append(params, uid)
}
sql.WriteString(" AND query_history.datasource_uid IN (? " + strings.Repeat(",?", len(query.DatasourceUIDs)-1) + ") ")
}
builder.Write(sql.String(), params...) builder.Write(sql.String(), params...)
} }