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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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.
Query parameters:
- **datasourceUid** - Filter the query history for selected data sources. You must specify at least one data source UID. To perform an "AND" filtering with multiple data sources, specify the data source parameter using the following format: `datasourceUid=uid1&datasourceUid=uid2`.
- **datasourceUid** - Filter the query history for the selected data source. To perform an "AND" filtering with multiple data sources, specify the data source parameter using the following format: `datasourceUid=uid1&datasourceUid=uid2`.
- **searchString** Filter the query history based on the content.
- **sort** - Specify the sorting order. Sorting can be `time-asc` or `time-desc`. The default is `time-desc`.
- **onlyStarred** - Search for queries that are starred. Defaults to `false`.
- **page** - Search supports pagination. Specify which page number to return. Use the limit parameter to specify the number of queries per page.
- **limit** - Limits the number of returned query history items per page. The default is 100 queries per page.
- **from/to** - Specifies time range for the query history search. The time can be either epoch timestamps in milliseconds or relative using Grafana time units. For example, now-5m.
**Example request for query history search**:

View File

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

View File

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

View File

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

View File

@ -5,18 +5,14 @@ package queryhistory
import (
"encoding/json"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
)
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",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", "test")
@ -39,6 +35,16 @@ func TestGetQueriesFromQueryHistory(t *testing.T) {
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",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.Req.Form.Add("datasourceUid", sc.initialResult.Result.DatasourceUID)
@ -114,4 +120,146 @@ func TestGetQueriesFromQueryHistory(t *testing.T) {
require.Equal(t, 2, response.Result.TotalCount)
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) {
params := []interface{}{user.OrgId, user.UserId, "%" + query.SearchString + "%", "%" + query.SearchString + "%"}
params := []interface{}{user.OrgId, user.UserId, query.From, query.To, "%" + query.SearchString + "%", "%" + query.SearchString + "%"}
var sql bytes.Buffer
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)
}
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(" AND query_history.datasource_uid IN (? " + strings.Repeat(",?", len(query.DatasourceUIDs)-1) + ") ")
}
builder.Write(sql.String(), params...)
}