| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | package expr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-04-12 02:20:10 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							| 
									
										
										
										
											2022-06-28 00:23:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | // Request is similar to plugins.DataQuery but with the Time Ranges is per Query.
 | 
					
						
							|  |  |  | type Request struct { | 
					
						
							|  |  |  | 	Headers map[string]string | 
					
						
							|  |  |  | 	Debug   bool | 
					
						
							|  |  |  | 	OrgId   int64 | 
					
						
							|  |  |  | 	Queries []Query | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	User    *user.SignedInUser | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Query is like plugins.DataSubQuery, but with a a time range, and only the UID
 | 
					
						
							|  |  |  | // for the data source. Also interval is a time.Duration.
 | 
					
						
							|  |  |  | type Query struct { | 
					
						
							|  |  |  | 	RefID         string | 
					
						
							|  |  |  | 	TimeRange     TimeRange | 
					
						
							| 
									
										
										
										
											2022-06-28 00:23:15 +08:00
										 |  |  | 	DataSource    *datasources.DataSource `json:"datasource"` | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | 	JSON          json.RawMessage | 
					
						
							|  |  |  | 	Interval      time.Duration | 
					
						
							|  |  |  | 	QueryType     string | 
					
						
							|  |  |  | 	MaxDataPoints int64 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TimeRange is a time.Time based TimeRange.
 | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | type TimeRange interface { | 
					
						
							|  |  |  | 	AbsoluteTime(now time.Time) backend.TimeRange | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type AbsoluteTimeRange struct { | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | 	From time.Time | 
					
						
							|  |  |  | 	To   time.Time | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | func (r AbsoluteTimeRange) AbsoluteTime(_ time.Time) backend.TimeRange { | 
					
						
							|  |  |  | 	return backend.TimeRange{ | 
					
						
							|  |  |  | 		From: r.From, | 
					
						
							|  |  |  | 		To:   r.To, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RelativeTimeRange is a time range relative to some absolute time.
 | 
					
						
							|  |  |  | type RelativeTimeRange struct { | 
					
						
							|  |  |  | 	From time.Duration | 
					
						
							|  |  |  | 	To   time.Duration | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r RelativeTimeRange) AbsoluteTime(t time.Time) backend.TimeRange { | 
					
						
							|  |  |  | 	return backend.TimeRange{ | 
					
						
							|  |  |  | 		From: t.Add(r.From), | 
					
						
							|  |  |  | 		To:   t.Add(r.To), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | // TransformData takes Queries which are either expressions nodes
 | 
					
						
							|  |  |  | // or are datasource requests.
 | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | func (s *Service) TransformData(ctx context.Context, now time.Time, req *Request) (r *backend.QueryDataResponse, err error) { | 
					
						
							| 
									
										
										
										
											2021-01-23 01:27:33 +08:00
										 |  |  | 	if s.isDisabled() { | 
					
						
							| 
									
										
										
										
											2021-05-26 21:15:21 +08:00
										 |  |  | 		return nil, fmt.Errorf("server side expressions are disabled") | 
					
						
							| 
									
										
										
										
											2021-01-23 01:27:33 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-01 21:48:02 +08:00
										 |  |  | 	start := time.Now() | 
					
						
							| 
									
										
										
										
											2023-04-18 20:04:51 +08:00
										 |  |  | 	ctx, span := s.tracer.Start(ctx, "SSE.TransformData") | 
					
						
							| 
									
										
										
										
											2021-02-01 21:48:02 +08:00
										 |  |  | 	defer func() { | 
					
						
							|  |  |  | 		var respStatus string | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case err == nil: | 
					
						
							|  |  |  | 			respStatus = "success" | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			respStatus = "failure" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		duration := float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond) | 
					
						
							| 
									
										
										
										
											2023-04-18 07:12:44 +08:00
										 |  |  | 		s.metrics.expressionsQuerySummary.WithLabelValues(respStatus).Observe(duration) | 
					
						
							| 
									
										
										
										
											2023-04-18 20:04:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		span.End() | 
					
						
							| 
									
										
										
										
											2021-02-01 21:48:02 +08:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	// Build the pipeline from the request, checking for ordering issues (e.g. loops)
 | 
					
						
							|  |  |  | 	// and parsing graph nodes from the queries.
 | 
					
						
							| 
									
										
										
										
											2021-01-23 01:27:33 +08:00
										 |  |  | 	pipeline, err := s.BuildPipeline(req) | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-05-26 21:15:21 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Execute the pipeline
 | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | 	responses, err := s.ExecutePipeline(ctx, now, pipeline) | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-05-26 21:15:21 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get which queries have the Hide property so they those queries' results
 | 
					
						
							|  |  |  | 	// can be excluded from the response.
 | 
					
						
							|  |  |  | 	hidden, err := hiddenRefIDs(req.Queries) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-05-26 21:15:21 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(hidden) != 0 { | 
					
						
							|  |  |  | 		filteredRes := backend.NewQueryDataResponse() | 
					
						
							|  |  |  | 		for refID, res := range responses.Responses { | 
					
						
							|  |  |  | 			if _, ok := hidden[refID]; !ok { | 
					
						
							|  |  |  | 				filteredRes.Responses[refID] = res | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		responses = filteredRes | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return responses, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | func hiddenRefIDs(queries []Query) (map[string]struct{}, error) { | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	hidden := make(map[string]struct{}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, query := range queries { | 
					
						
							|  |  |  | 		hide := struct { | 
					
						
							|  |  |  | 			Hide bool `json:"hide"` | 
					
						
							|  |  |  | 		}{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := json.Unmarshal(query.JSON, &hide); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if hide.Hide { | 
					
						
							|  |  |  | 			hidden[query.RefID] = struct{}{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return hidden, nil | 
					
						
							|  |  |  | } |