Merge pull request #2222 from influxdata/fix/builder-template-vars
Fix rendering of templated queries to the /queries endpointpull/2275/merge
commit
ae13f6ef23
|
@ -4,6 +4,7 @@
|
|||
1. [#2158](https://github.com/influxdata/chronograf/pull/2158): Fix 'Cannot connect to source' false error flag on Dashboard page
|
||||
1. [#2167](https://github.com/influxdata/chronograf/pull/2167): Add fractions of seconds to time field in csv export
|
||||
1. [#1077](https://github.com/influxdata/chronograf/pull/2087): Fix Chronograf requiring Telegraf's CPU and system plugins to ensure that all Apps appear on the HOST LIST page.
|
||||
1. [#2222](https://github.com/influxdata/chronograf/pull/2222): Fix template variables in dashboard query building.
|
||||
|
||||
### Features
|
||||
### UI Improvements
|
||||
|
|
|
@ -12,16 +12,21 @@ import (
|
|||
"github.com/influxdata/chronograf/influx/queries"
|
||||
)
|
||||
|
||||
// QueryRequest is query that will be converted to a queryConfig
|
||||
type QueryRequest struct {
|
||||
ID string `json:"id"`
|
||||
Query string `json:"query"`
|
||||
ID string `json:"id"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// QueriesRequest converts all queries to queryConfigs with the help
|
||||
// of the template variables
|
||||
type QueriesRequest struct {
|
||||
Queries []QueryRequest `json:"queries"`
|
||||
TemplateVars chronograf.TemplateVars `json:"tempVars,omitempty"`
|
||||
}
|
||||
|
||||
type QueriesRequest struct {
|
||||
Queries []QueryRequest `json:"queries"`
|
||||
}
|
||||
|
||||
// QueryResponse is the return result of a QueryRequest including
|
||||
// the raw query, the templated query, the queryConfig and the queryAST
|
||||
type QueryResponse struct {
|
||||
ID string `json:"id"`
|
||||
Query string `json:"query"`
|
||||
|
@ -31,11 +36,12 @@ type QueryResponse struct {
|
|||
TemplateVars chronograf.TemplateVars `json:"tempVars,omitempty"`
|
||||
}
|
||||
|
||||
// QueriesResponse is the response for a QueriesRequest
|
||||
type QueriesResponse struct {
|
||||
Queries []QueryResponse `json:"queries"`
|
||||
}
|
||||
|
||||
// Queries parses InfluxQL and returns the JSON
|
||||
// Queries analyzes InfluxQL to produce front-end friendly QueryConfig
|
||||
func (s *Service) Queries(w http.ResponseWriter, r *http.Request) {
|
||||
srcID, err := paramID("id", r)
|
||||
if err != nil {
|
||||
|
@ -66,12 +72,7 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) {
|
|||
Query: q.Query,
|
||||
}
|
||||
|
||||
query := q.Query
|
||||
if len(q.TemplateVars) > 0 {
|
||||
query = influx.TemplateReplace(query, q.TemplateVars)
|
||||
qr.QueryTemplated = &query
|
||||
}
|
||||
|
||||
query := influx.TemplateReplace(q.Query, req.TemplateVars)
|
||||
qc := ToQueryConfig(query)
|
||||
if err := s.DefaultRP(ctx, &qc, &src); err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
|
@ -83,9 +84,10 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) {
|
|||
qr.QueryAST = stmt
|
||||
}
|
||||
|
||||
if len(q.TemplateVars) > 0 {
|
||||
qr.TemplateVars = q.TemplateVars
|
||||
if len(req.TemplateVars) > 0 {
|
||||
qr.TemplateVars = req.TemplateVars
|
||||
qr.QueryConfig.RawText = &qr.Query
|
||||
qr.QueryTemplated = &query
|
||||
}
|
||||
|
||||
qr.QueryConfig.ID = q.ID
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/mocks"
|
||||
)
|
||||
|
||||
func TestService_Queries(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
SourcesStore chronograf.SourcesStore
|
||||
ID string
|
||||
w *httptest.ResponseRecorder
|
||||
r *http.Request
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "bad json",
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: ID,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`howdy`))),
|
||||
want: `{"code":400,"message":"Unparsable JSON"}`,
|
||||
},
|
||||
{
|
||||
name: "bad id",
|
||||
ID: "howdy",
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte{})),
|
||||
want: `{"code":422,"message":"Error converting ID howdy"}`,
|
||||
},
|
||||
{
|
||||
name: "query with no template vars",
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: ID,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`{
|
||||
"queries": [
|
||||
{
|
||||
"query": "SELECT \"pingReq\" FROM db.\"monitor\".\"httpd\" WHERE time > now() - 1m",
|
||||
"id": "82b60d37-251e-4afe-ac93-ca20a3642b11"
|
||||
}
|
||||
]}`))),
|
||||
want: `{"queries":[{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM db.\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"db","measurement":"httpd","retentionPolicy":"monitor","fields":[{"value":"pingReq","type":"field","alias":""}],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":null,"range":{"upper":"","lower":"now() - 1m"}},"queryAST":{"condition":{"expr":"binary","op":"\u003e","lhs":{"expr":"reference","val":"time"},"rhs":{"expr":"binary","op":"-","lhs":{"expr":"call","name":"now"},"rhs":{"expr":"literal","val":"1m","type":"duration"}}},"fields":[{"column":{"expr":"reference","val":"pingReq"}}],"sources":[{"database":"db","retentionPolicy":"monitor","name":"httpd","type":"measurement"}]}}]}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "query with unparsable query",
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: ID,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`{
|
||||
"queries": [
|
||||
{
|
||||
"query": "SHOW DATABASES",
|
||||
"id": "82b60d37-251e-4afe-ac93-ca20a3642b11"
|
||||
}
|
||||
]}`))),
|
||||
want: `{"queries":[{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SHOW DATABASES","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SHOW DATABASES","range":null}}]}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "query with template vars",
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: ID,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`{
|
||||
"queries": [
|
||||
{
|
||||
"query": "SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time > now() - 1m",
|
||||
"id": "82b60d37-251e-4afe-ac93-ca20a3642b11"
|
||||
}
|
||||
],
|
||||
"tempVars": [
|
||||
{
|
||||
"tempVar": ":dbs:",
|
||||
"values": [
|
||||
{
|
||||
"value": "_internal",
|
||||
"type": "database",
|
||||
"selected": true
|
||||
}
|
||||
],
|
||||
"id": "792eda0d-2bb2-4de6-a86f-1f652889b044",
|
||||
"type": "databases",
|
||||
"label": "",
|
||||
"query": {
|
||||
"influxql": "SHOW DATABASES",
|
||||
"measurement": "",
|
||||
"tagKey": "",
|
||||
"fieldKey": ""
|
||||
},
|
||||
"links": {
|
||||
"self": "/chronograf/v1/dashboards/1/templates/792eda0d-2bb2-4de6-a86f-1f652889b044"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dashtime",
|
||||
"tempVar": ":dashboardTime:",
|
||||
"type": "constant",
|
||||
"values": [
|
||||
{
|
||||
"value": "now() - 15m",
|
||||
"type": "constant",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "upperdashtime",
|
||||
"tempVar": ":upperDashboardTime:",
|
||||
"type": "constant",
|
||||
"values": [
|
||||
{
|
||||
"value": "now()",
|
||||
"type": "constant",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "interval",
|
||||
"type": "constant",
|
||||
"tempVar": ":interval:",
|
||||
"resolution": 1000,
|
||||
"reportingInterval": 10000000000,
|
||||
"values": []
|
||||
}
|
||||
]
|
||||
}`))),
|
||||
want: `{"queries":[{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"_internal","measurement":"httpd","retentionPolicy":"monitor","fields":[{"value":"pingReq","type":"field","alias":""}],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","range":{"upper":"","lower":"now() - 1m"}},"queryAST":{"condition":{"expr":"binary","op":"\u003e","lhs":{"expr":"reference","val":"time"},"rhs":{"expr":"binary","op":"-","lhs":{"expr":"call","name":"now"},"rhs":{"expr":"literal","val":"1m","type":"duration"}}},"fields":[{"column":{"expr":"reference","val":"pingReq"}}],"sources":[{"database":"_internal","retentionPolicy":"monitor","name":"httpd","type":"measurement"}]},"queryTemplated":"SELECT \"pingReq\" FROM \"_internal\".\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","tempVars":[{"tempVar":":dbs:","values":[{"value":"_internal","type":"database","selected":true}]},{"tempVar":":dashboardTime:","values":[{"value":"now() - 15m","type":"constant","selected":true}]},{"tempVar":":upperDashboardTime:","values":[{"value":"now()","type":"constant","selected":true}]},{"tempVar":":interval:","duration":60000000000,"resolution":1000,"reportingInterval":10000000000}]}]}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.r = tt.r.WithContext(httprouter.WithParams(
|
||||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.ID,
|
||||
},
|
||||
}))
|
||||
s := &Service{
|
||||
SourcesStore: tt.SourcesStore,
|
||||
Logger: &mocks.TestLogger{},
|
||||
}
|
||||
s.Queries(tt.w, tt.r)
|
||||
got := tt.w.Body.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("got:\n%s\nwant:\n%s\n", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue