2017-02-06 15:40:05 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2017-04-14 00:49:53 +00:00
|
|
|
"fmt"
|
2017-02-06 15:40:05 +00:00
|
|
|
"net/http"
|
2017-11-08 17:27:35 +00:00
|
|
|
"time"
|
2017-02-06 15:40:05 +00:00
|
|
|
|
2017-04-14 00:49:53 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
2017-04-07 21:57:06 +00:00
|
|
|
"github.com/influxdata/chronograf"
|
2017-04-28 20:12:28 +00:00
|
|
|
"github.com/influxdata/chronograf/influx"
|
2017-02-06 15:40:05 +00:00
|
|
|
"github.com/influxdata/chronograf/influx/queries"
|
|
|
|
)
|
|
|
|
|
2017-11-02 22:57:02 +00:00
|
|
|
// QueryRequest is query that will be converted to a queryConfig
|
2017-02-06 15:40:05 +00:00
|
|
|
type QueryRequest struct {
|
2017-11-02 22:57:02 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
Query string `json:"query"`
|
2017-02-06 15:40:05 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 22:57:02 +00:00
|
|
|
// QueriesRequest converts all queries to queryConfigs with the help
|
|
|
|
// of the template variables
|
2017-04-07 21:57:06 +00:00
|
|
|
type QueriesRequest struct {
|
2017-11-08 17:27:35 +00:00
|
|
|
Queries []QueryRequest `json:"queries"`
|
|
|
|
TemplateVars []chronograf.TemplateVar `json:"tempVars,omitempty"`
|
2017-04-07 21:57:06 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 22:57:02 +00:00
|
|
|
// QueryResponse is the return result of a QueryRequest including
|
|
|
|
// the raw query, the templated query, the queryConfig and the queryAST
|
2017-04-07 21:57:06 +00:00
|
|
|
type QueryResponse struct {
|
2018-06-26 20:10:38 +00:00
|
|
|
Duration int64 `json:"durationMs"`
|
2017-04-28 20:12:28 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
Query string `json:"query"`
|
|
|
|
QueryConfig chronograf.QueryConfig `json:"queryConfig"`
|
|
|
|
QueryAST *queries.SelectStatement `json:"queryAST,omitempty"`
|
|
|
|
QueryTemplated *string `json:"queryTemplated,omitempty"`
|
2017-04-07 21:57:06 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 22:57:02 +00:00
|
|
|
// QueriesResponse is the response for a QueriesRequest
|
2017-04-07 21:57:06 +00:00
|
|
|
type QueriesResponse struct {
|
|
|
|
Queries []QueryResponse `json:"queries"`
|
|
|
|
}
|
|
|
|
|
2017-11-05 01:19:08 +00:00
|
|
|
// Queries analyzes InfluxQL to produce front-end friendly QueryConfig
|
2017-04-07 21:06:24 +00:00
|
|
|
func (s *Service) Queries(w http.ResponseWriter, r *http.Request) {
|
2017-04-07 21:57:06 +00:00
|
|
|
srcID, err := paramID("id", r)
|
|
|
|
if err != nil {
|
|
|
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := r.Context()
|
2017-10-31 20:41:17 +00:00
|
|
|
src, err := s.Store.Sources(ctx).Get(ctx, srcID)
|
2017-04-14 00:49:53 +00:00
|
|
|
if err != nil {
|
2017-04-07 21:57:06 +00:00
|
|
|
notFound(w, srcID, s.Logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var req QueriesRequest
|
2017-02-06 15:40:05 +00:00
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
|
invalidJSON(w, s.Logger)
|
|
|
|
return
|
|
|
|
}
|
2017-04-07 21:57:06 +00:00
|
|
|
res := QueriesResponse{
|
|
|
|
Queries: make([]QueryResponse, len(req.Queries)),
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, q := range req.Queries {
|
|
|
|
qr := QueryResponse{
|
2017-04-14 00:49:53 +00:00
|
|
|
ID: q.ID,
|
|
|
|
Query: q.Query,
|
2017-04-07 21:57:06 +00:00
|
|
|
}
|
2017-04-14 00:49:53 +00:00
|
|
|
|
2018-06-28 19:42:28 +00:00
|
|
|
qc := ToQueryConfig(q.Query)
|
2017-04-14 00:49:53 +00:00
|
|
|
if err := s.DefaultRP(ctx, &qc, &src); err != nil {
|
|
|
|
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
|
|
|
return
|
|
|
|
}
|
2017-11-10 19:06:48 +00:00
|
|
|
qc.Shifts = []chronograf.TimeShift{}
|
2017-04-14 00:49:53 +00:00
|
|
|
qr.QueryConfig = qc
|
|
|
|
|
2018-06-28 19:42:28 +00:00
|
|
|
if stmt, err := queries.ParseSelect(q.Query); err == nil {
|
2017-04-07 21:57:06 +00:00
|
|
|
qr.QueryAST = stmt
|
|
|
|
}
|
2017-04-14 00:49:53 +00:00
|
|
|
|
2018-06-28 19:42:28 +00:00
|
|
|
if dur, err := influx.ParseTime(q.Query, time.Now()); err == nil {
|
2018-06-26 20:10:38 +00:00
|
|
|
ms := dur.Nanoseconds() / int64(time.Millisecond)
|
|
|
|
if ms == 0 {
|
|
|
|
ms = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
qr.Duration = ms
|
|
|
|
}
|
|
|
|
|
2017-04-10 18:02:09 +00:00
|
|
|
qr.QueryConfig.ID = q.ID
|
2017-04-07 21:57:06 +00:00
|
|
|
res.Queries[i] = qr
|
2017-02-06 15:40:05 +00:00
|
|
|
}
|
|
|
|
|
2017-04-07 21:57:06 +00:00
|
|
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
2017-02-06 15:40:05 +00:00
|
|
|
}
|
2017-04-14 00:49:53 +00:00
|
|
|
|
|
|
|
// DefaultRP will add the default retention policy to the QC if one has not been specified
|
|
|
|
func (s *Service) DefaultRP(ctx context.Context, qc *chronograf.QueryConfig, src *chronograf.Source) error {
|
|
|
|
// Only need to find the default RP IFF the qc's rp is empty
|
|
|
|
if qc.RetentionPolicy != "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// For queries without databases, measurements, or fields we will not
|
|
|
|
// be able to find an RP
|
|
|
|
if qc.Database == "" || qc.Measurement == "" || len(qc.Fields) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
db := s.Databases
|
|
|
|
if err := db.Connect(ctx, src); err != nil {
|
|
|
|
return fmt.Errorf("Unable to connect to source: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rps, err := db.AllRP(ctx, qc.Database)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to load RPs from DB %s: %v", qc.Database, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rp := range rps {
|
|
|
|
if rp.Default {
|
|
|
|
qc.RetentionPolicy = rp.Name
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|