1package expr 2 3import ( 4 "encoding/json" 5 "fmt" 6 "time" 7 8 "github.com/grafana/grafana-plugin-sdk-go/backend" 9 "github.com/grafana/grafana/pkg/bus" 10 "github.com/grafana/grafana/pkg/models" 11 "github.com/grafana/grafana/pkg/plugins/adapters" 12 "github.com/grafana/grafana/pkg/tsdb/legacydata" 13 "github.com/grafana/grafana/pkg/util/errutil" 14 "github.com/prometheus/client_golang/prometheus" 15 "golang.org/x/net/context" 16) 17 18var ( 19 expressionsQuerySummary *prometheus.SummaryVec 20) 21 22func init() { 23 expressionsQuerySummary = prometheus.NewSummaryVec( 24 prometheus.SummaryOpts{ 25 Name: "expressions_queries_duration_milliseconds", 26 Help: "Expressions query summary", 27 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 28 }, 29 []string{"status"}, 30 ) 31 32 prometheus.MustRegister(expressionsQuerySummary) 33} 34 35// WrapTransformData creates and executes transform requests 36func (s *Service) WrapTransformData(ctx context.Context, query legacydata.DataQuery) (*backend.QueryDataResponse, error) { 37 req := Request{ 38 OrgId: query.User.OrgId, 39 Queries: []Query{}, 40 } 41 42 for _, q := range query.Queries { 43 if q.DataSource == nil { 44 return nil, fmt.Errorf("mising datasource info: " + q.RefID) 45 } 46 modelJSON, err := q.Model.MarshalJSON() 47 if err != nil { 48 return nil, err 49 } 50 req.Queries = append(req.Queries, Query{ 51 JSON: modelJSON, 52 Interval: time.Duration(q.IntervalMS) * time.Millisecond, 53 RefID: q.RefID, 54 MaxDataPoints: q.MaxDataPoints, 55 QueryType: q.QueryType, 56 Datasource: DataSourceRef{ 57 Type: q.DataSource.Type, 58 UID: q.DataSource.Uid, 59 }, 60 TimeRange: TimeRange{ 61 From: query.TimeRange.GetFromAsTimeUTC(), 62 To: query.TimeRange.GetToAsTimeUTC(), 63 }, 64 }) 65 } 66 return s.TransformData(ctx, &req) 67} 68 69// Request is similar to plugins.DataQuery but with the Time Ranges is per Query. 70type Request struct { 71 Headers map[string]string 72 Debug bool 73 OrgId int64 74 Queries []Query 75} 76 77// Query is like plugins.DataSubQuery, but with a a time range, and only the UID 78// for the data source. Also interval is a time.Duration. 79type Query struct { 80 RefID string 81 TimeRange TimeRange 82 DatasourceUID string // deprecated, value -100 when expressions 83 Datasource DataSourceRef `json:"datasource"` 84 JSON json.RawMessage 85 Interval time.Duration 86 QueryType string 87 MaxDataPoints int64 88} 89 90type DataSourceRef struct { 91 Type string `json:"type"` // value should be __expr__ 92 UID string `json:"uid"` // value should be __expr__ 93} 94 95func (q *Query) GetDatasourceUID() string { 96 if q.DatasourceUID != "" { 97 return q.DatasourceUID // backwards compatibility gets precedence 98 } 99 100 if q.Datasource.UID != "" { 101 return q.Datasource.UID 102 } 103 return "" 104} 105 106// TimeRange is a time.Time based TimeRange. 107type TimeRange struct { 108 From time.Time 109 To time.Time 110} 111 112// TransformData takes Queries which are either expressions nodes 113// or are datasource requests. 114func (s *Service) TransformData(ctx context.Context, req *Request) (r *backend.QueryDataResponse, err error) { 115 if s.isDisabled() { 116 return nil, fmt.Errorf("server side expressions are disabled") 117 } 118 119 start := time.Now() 120 defer func() { 121 var respStatus string 122 switch { 123 case err == nil: 124 respStatus = "success" 125 default: 126 respStatus = "failure" 127 } 128 duration := float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond) 129 expressionsQuerySummary.WithLabelValues(respStatus).Observe(duration) 130 }() 131 132 // Build the pipeline from the request, checking for ordering issues (e.g. loops) 133 // and parsing graph nodes from the queries. 134 pipeline, err := s.BuildPipeline(req) 135 if err != nil { 136 return nil, err 137 } 138 139 // Execute the pipeline 140 responses, err := s.ExecutePipeline(ctx, pipeline) 141 if err != nil { 142 return nil, err 143 } 144 145 // Get which queries have the Hide property so they those queries' results 146 // can be excluded from the response. 147 hidden, err := hiddenRefIDs(req.Queries) 148 if err != nil { 149 return nil, err 150 } 151 152 if len(hidden) != 0 { 153 filteredRes := backend.NewQueryDataResponse() 154 for refID, res := range responses.Responses { 155 if _, ok := hidden[refID]; !ok { 156 filteredRes.Responses[refID] = res 157 } 158 } 159 responses = filteredRes 160 } 161 162 return responses, nil 163} 164 165func hiddenRefIDs(queries []Query) (map[string]struct{}, error) { 166 hidden := make(map[string]struct{}) 167 168 for _, query := range queries { 169 hide := struct { 170 Hide bool `json:"hide"` 171 }{} 172 173 if err := json.Unmarshal(query.JSON, &hide); err != nil { 174 return nil, err 175 } 176 177 if hide.Hide { 178 hidden[query.RefID] = struct{}{} 179 } 180 } 181 return hidden, nil 182} 183 184// queryData is called used to query datasources that are not expression commands, but are used 185// alongside expressions and/or are the input of an expression command. 186func (s *Service) queryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { 187 if len(req.Queries) == 0 { 188 return nil, fmt.Errorf("zero queries found in datasource request") 189 } 190 191 datasourceID := int64(0) 192 var datasourceUID string 193 194 if req.PluginContext.DataSourceInstanceSettings != nil { 195 datasourceID = req.PluginContext.DataSourceInstanceSettings.ID 196 datasourceUID = req.PluginContext.DataSourceInstanceSettings.UID 197 } 198 199 getDsInfo := &models.GetDataSourceQuery{ 200 OrgId: req.PluginContext.OrgID, 201 Id: datasourceID, 202 Uid: datasourceUID, 203 } 204 205 if err := bus.DispatchCtx(ctx, getDsInfo); err != nil { 206 return nil, fmt.Errorf("could not find datasource: %w", err) 207 } 208 209 dsInstanceSettings, err := adapters.ModelToInstanceSettings(getDsInfo.Result, s.decryptSecureJsonDataFn(ctx)) 210 if err != nil { 211 return nil, errutil.Wrap("failed to convert datasource instance settings", err) 212 } 213 214 req.PluginContext.DataSourceInstanceSettings = dsInstanceSettings 215 req.PluginContext.PluginID = getDsInfo.Result.Type 216 217 return s.dataService.QueryData(ctx, req) 218} 219 220func (s *Service) decryptSecureJsonDataFn(ctx context.Context) func(map[string][]byte) map[string]string { 221 return func(m map[string][]byte) map[string]string { 222 decryptedJsonData, err := s.secretsService.DecryptJsonData(ctx, m) 223 if err != nil { 224 logger.Error("Failed to decrypt secure json data", "error", err) 225 } 226 return decryptedJsonData 227 } 228} 229