| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | package expr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | 
					
						
							| 
									
										
										
										
											2021-03-03 02:51:33 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/expr/classic" | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/expr/mathexp" | 
					
						
							| 
									
										
										
										
											2021-05-07 21:16:21 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/plugins/adapters" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/util/errutil" | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"gonum.org/v1/gonum/graph/simple" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-07 21:16:21 +08:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	logger = log.New("expr") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | type QueryError struct { | 
					
						
							|  |  |  | 	RefID string | 
					
						
							|  |  |  | 	Err   error | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e QueryError) Error() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("failed to execute query %s: %s", e.RefID, e.Err) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 18:07:24 +08:00
										 |  |  | func (e QueryError) Unwrap() error { | 
					
						
							|  |  |  | 	return e.Err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-27 20:57:41 +08:00
										 |  |  | // baseNode includes common properties used across DPNodes.
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | type baseNode struct { | 
					
						
							|  |  |  | 	id    int64 | 
					
						
							|  |  |  | 	refID string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type rawNode struct { | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 	RefID      string `json:"refId"` | 
					
						
							|  |  |  | 	Query      map[string]interface{} | 
					
						
							|  |  |  | 	QueryType  string | 
					
						
							|  |  |  | 	TimeRange  TimeRange | 
					
						
							|  |  |  | 	DataSource *models.DataSource | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (rn *rawNode) GetCommandType() (c CommandType, err error) { | 
					
						
							|  |  |  | 	rawType, ok := rn.Query["type"] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return c, fmt.Errorf("no expression command type in query for refId %v", rn.RefID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	typeString, ok := rawType.(string) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return c, fmt.Errorf("expected expression command type to be a string, got type %T", rawType) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ParseCommandType(typeString) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // String returns a string representation of the node. In particular for
 | 
					
						
							| 
									
										
										
										
											2021-06-10 07:59:44 +08:00
										 |  |  | // %v formatting in error messages.
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | func (b *baseNode) String() string { | 
					
						
							|  |  |  | 	return b.refID | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CMDNode is a DPNode that holds an expression command.
 | 
					
						
							|  |  |  | type CMDNode struct { | 
					
						
							|  |  |  | 	baseNode | 
					
						
							|  |  |  | 	CMDType CommandType | 
					
						
							|  |  |  | 	Command Command | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ID returns the id of the node so it can fulfill the gonum's graph Node interface.
 | 
					
						
							|  |  |  | func (b *baseNode) ID() int64 { | 
					
						
							|  |  |  | 	return b.id | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RefID returns the refId of the node.
 | 
					
						
							|  |  |  | func (b *baseNode) RefID() string { | 
					
						
							|  |  |  | 	return b.refID | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NodeType returns the data pipeline node type.
 | 
					
						
							|  |  |  | func (gn *CMDNode) NodeType() NodeType { | 
					
						
							|  |  |  | 	return TypeCMDNode | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Execute runs the node and adds the results to vars. If the node requires
 | 
					
						
							|  |  |  | // other nodes they must have already been executed and their results must
 | 
					
						
							|  |  |  | // already by in vars.
 | 
					
						
							| 
									
										
										
										
											2021-03-08 14:02:49 +08:00
										 |  |  | func (gn *CMDNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (mathexp.Results, error) { | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	return gn.Command.Execute(ctx, vars) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func buildCMDNode(dp *simple.DirectedGraph, rn *rawNode) (*CMDNode, error) { | 
					
						
							|  |  |  | 	commandType, err := rn.GetCommandType() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("invalid expression command type in '%v'", rn.RefID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	node := &CMDNode{ | 
					
						
							|  |  |  | 		baseNode: baseNode{ | 
					
						
							|  |  |  | 			id:    dp.NewNode().ID(), | 
					
						
							|  |  |  | 			refID: rn.RefID, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-04-27 19:22:11 +08:00
										 |  |  | 		CMDType: commandType, | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch commandType { | 
					
						
							|  |  |  | 	case TypeMath: | 
					
						
							|  |  |  | 		node.Command, err = UnmarshalMathCommand(rn) | 
					
						
							|  |  |  | 	case TypeReduce: | 
					
						
							|  |  |  | 		node.Command, err = UnmarshalReduceCommand(rn) | 
					
						
							|  |  |  | 	case TypeResample: | 
					
						
							|  |  |  | 		node.Command, err = UnmarshalResampleCommand(rn) | 
					
						
							| 
									
										
										
										
											2021-03-03 02:51:33 +08:00
										 |  |  | 	case TypeClassicConditions: | 
					
						
							|  |  |  | 		node.Command, err = classic.UnmarshalConditionsCmd(rn.Query, rn.RefID) | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("expression command type '%v' in '%v' not implemented", commandType, rn.RefID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return node, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	defaultIntervalMS = int64(64) | 
					
						
							|  |  |  | 	defaultMaxDP      = int64(5000) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DSNode is a DPNode that holds a datasource request.
 | 
					
						
							|  |  |  | type DSNode struct { | 
					
						
							|  |  |  | 	baseNode | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 	query      json.RawMessage | 
					
						
							|  |  |  | 	datasource *models.DataSource | 
					
						
							| 
									
										
										
										
											2021-01-16 00:33:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	orgID      int64 | 
					
						
							|  |  |  | 	queryType  string | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | 	timeRange  TimeRange | 
					
						
							| 
									
										
										
										
											2021-01-16 00:33:50 +08:00
										 |  |  | 	intervalMS int64 | 
					
						
							|  |  |  | 	maxDP      int64 | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 	request    Request | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NodeType returns the data pipeline node type.
 | 
					
						
							|  |  |  | func (dn *DSNode) NodeType() NodeType { | 
					
						
							|  |  |  | 	return TypeDatasourceNode | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Request) (*DSNode, error) { | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	encodedQuery, err := json.Marshal(rn.Query) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dsNode := &DSNode{ | 
					
						
							|  |  |  | 		baseNode: baseNode{ | 
					
						
							|  |  |  | 			id:    dp.NewNode().ID(), | 
					
						
							|  |  |  | 			refID: rn.RefID, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 		orgID:      req.OrgId, | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		query:      json.RawMessage(encodedQuery), | 
					
						
							|  |  |  | 		queryType:  rn.QueryType, | 
					
						
							|  |  |  | 		intervalMS: defaultIntervalMS, | 
					
						
							|  |  |  | 		maxDP:      defaultMaxDP, | 
					
						
							|  |  |  | 		timeRange:  rn.TimeRange, | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 		request:    *req, | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 		datasource: rn.DataSource, | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var floatIntervalMS float64 | 
					
						
							| 
									
										
										
										
											2021-08-10 15:59:48 +08:00
										 |  |  | 	if rawIntervalMS, ok := rn.Query["intervalMs"]; ok { | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		if floatIntervalMS, ok = rawIntervalMS.(float64); !ok { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("expected intervalMs to be an float64, got type %T for refId %v", rawIntervalMS, rn.RefID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		dsNode.intervalMS = int64(floatIntervalMS) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var floatMaxDP float64 | 
					
						
							| 
									
										
										
										
											2021-08-10 15:59:48 +08:00
										 |  |  | 	if rawMaxDP, ok := rn.Query["maxDataPoints"]; ok { | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		if floatMaxDP, ok = rawMaxDP.(float64); !ok { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("expected maxDataPoints to be an float64, got type %T for refId %v", rawMaxDP, rn.RefID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		dsNode.maxDP = int64(floatMaxDP) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return dsNode, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Execute runs the node and adds the results to vars. If the node requires
 | 
					
						
							|  |  |  | // other nodes they must have already been executed and their results must
 | 
					
						
							|  |  |  | // already by in vars.
 | 
					
						
							| 
									
										
										
										
											2021-03-08 14:02:49 +08:00
										 |  |  | func (dn *DSNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (mathexp.Results, error) { | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 	dsInstanceSettings, err := adapters.ModelToInstanceSettings(dn.datasource, s.decryptSecureJsonDataFn(ctx)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return mathexp.Results{}, errutil.Wrap("failed to convert datasource instance settings", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	pc := backend.PluginContext{ | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 		OrgID:                      dn.orgID, | 
					
						
							|  |  |  | 		DataSourceInstanceSettings: dsInstanceSettings, | 
					
						
							|  |  |  | 		PluginID:                   dn.datasource.Type, | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	q := []backend.DataQuery{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			RefID:         dn.refID, | 
					
						
							|  |  |  | 			MaxDataPoints: dn.maxDP, | 
					
						
							|  |  |  | 			Interval:      time.Duration(int64(time.Millisecond) * dn.intervalMS), | 
					
						
							|  |  |  | 			JSON:          dn.query, | 
					
						
							| 
									
										
										
										
											2021-04-23 22:52:32 +08:00
										 |  |  | 			TimeRange: backend.TimeRange{ | 
					
						
							|  |  |  | 				From: dn.timeRange.From, | 
					
						
							|  |  |  | 				To:   dn.timeRange.To, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			QueryType: dn.queryType, | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 	resp, err := s.dataService.QueryData(ctx, &backend.QueryDataRequest{ | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		PluginContext: pc, | 
					
						
							|  |  |  | 		Queries:       q, | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 		Headers:       dn.request.Headers, | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return mathexp.Results{}, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vals := make([]mathexp.Value, 0) | 
					
						
							|  |  |  | 	for refID, qr := range resp.Responses { | 
					
						
							| 
									
										
										
										
											2021-03-24 00:11:15 +08:00
										 |  |  | 		if qr.Error != nil { | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | 			return mathexp.Results{}, QueryError{RefID: refID, Err: qr.Error} | 
					
						
							| 
									
										
										
										
											2021-03-24 00:11:15 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		if len(qr.Frames) == 1 { | 
					
						
							|  |  |  | 			frame := qr.Frames[0] | 
					
						
							|  |  |  | 			if frame.TimeSeriesSchema().Type == data.TimeSeriesTypeNot && isNumberTable(frame) { | 
					
						
							| 
									
										
										
										
											2021-05-07 21:16:21 +08:00
										 |  |  | 				logger.Debug("expression datasource query (numberSet)", "query", refID) | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 				numberSet, err := extractNumberSet(frame) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					return mathexp.Results{}, err | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				for _, n := range numberSet { | 
					
						
							|  |  |  | 					vals = append(vals, n) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				return mathexp.Results{ | 
					
						
							|  |  |  | 					Values: vals, | 
					
						
							|  |  |  | 				}, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 21:53:27 +08:00
										 |  |  | 		dataSource := dn.datasource.Type | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		for _, frame := range qr.Frames { | 
					
						
							| 
									
										
										
										
											2021-05-07 21:16:21 +08:00
										 |  |  | 			logger.Debug("expression datasource query (seriesSet)", "query", refID) | 
					
						
							| 
									
										
										
										
											2022-01-27 21:53:27 +08:00
										 |  |  | 			// Check for TimeSeriesTypeNot in InfluxDB queries. A data frame of this type will cause
 | 
					
						
							|  |  |  | 			// the WideToMany() function to error out, which results in unhealthy alerts.
 | 
					
						
							|  |  |  | 			// This check should be removed once inconsistencies in data source responses are solved.
 | 
					
						
							|  |  |  | 			if frame.TimeSeriesSchema().Type == data.TimeSeriesTypeNot && dataSource == models.DS_INFLUXDB { | 
					
						
							|  |  |  | 				logger.Warn("ignoring InfluxDB data frame due to missing numeric fields", "frame", frame) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 			series, err := WideToMany(frame) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return mathexp.Results{}, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			for _, s := range series { | 
					
						
							|  |  |  | 				vals = append(vals, s) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return mathexp.Results{ | 
					
						
							|  |  |  | 		Values: vals, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func isNumberTable(frame *data.Frame) bool { | 
					
						
							|  |  |  | 	if frame == nil || frame.Fields == nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	numericCount := 0 | 
					
						
							|  |  |  | 	stringCount := 0 | 
					
						
							|  |  |  | 	otherCount := 0 | 
					
						
							|  |  |  | 	for _, field := range frame.Fields { | 
					
						
							|  |  |  | 		fType := field.Type() | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case fType.Numeric(): | 
					
						
							|  |  |  | 			numericCount++ | 
					
						
							|  |  |  | 		case fType == data.FieldTypeString || fType == data.FieldTypeNullableString: | 
					
						
							|  |  |  | 			stringCount++ | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			otherCount++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return numericCount == 1 && otherCount == 0 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func extractNumberSet(frame *data.Frame) ([]mathexp.Number, error) { | 
					
						
							|  |  |  | 	numericField := 0 | 
					
						
							|  |  |  | 	stringFieldIdxs := []int{} | 
					
						
							|  |  |  | 	stringFieldNames := []string{} | 
					
						
							|  |  |  | 	for i, field := range frame.Fields { | 
					
						
							|  |  |  | 		fType := field.Type() | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case fType.Numeric(): | 
					
						
							|  |  |  | 			numericField = i | 
					
						
							|  |  |  | 		case fType == data.FieldTypeString || fType == data.FieldTypeNullableString: | 
					
						
							|  |  |  | 			stringFieldIdxs = append(stringFieldIdxs, i) | 
					
						
							|  |  |  | 			stringFieldNames = append(stringFieldNames, field.Name) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	numbers := make([]mathexp.Number, frame.Rows()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for rowIdx := 0; rowIdx < frame.Rows(); rowIdx++ { | 
					
						
							|  |  |  | 		val, _ := frame.FloatAt(numericField, rowIdx) | 
					
						
							|  |  |  | 		var labels data.Labels | 
					
						
							|  |  |  | 		for i := 0; i < len(stringFieldIdxs); i++ { | 
					
						
							|  |  |  | 			if i == 0 { | 
					
						
							|  |  |  | 				labels = make(data.Labels) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			key := stringFieldNames[i] // TODO check for duplicate string column names
 | 
					
						
							|  |  |  | 			val, _ := frame.ConcreteAt(stringFieldIdxs[i], rowIdx) | 
					
						
							|  |  |  | 			labels[key] = val.(string) // TODO check assertion / return error
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		n := mathexp.NewNumber("", labels) | 
					
						
							| 
									
										
										
										
											2022-03-10 23:03:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// The new value fields' configs gets pointed to the one in the original frame
 | 
					
						
							|  |  |  | 		n.Frame.Fields[0].Config = frame.Fields[numericField].Config | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		n.SetValue(&val) | 
					
						
							| 
									
										
										
										
											2022-03-10 23:03:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		numbers[rowIdx] = n | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return numbers, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WideToMany converts a data package wide type Frame to one or multiple Series. A series
 | 
					
						
							|  |  |  | // is created for each value type column of wide frame.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This might not be a good idea long term, but works now as an adapter/shim.
 | 
					
						
							|  |  |  | func WideToMany(frame *data.Frame) ([]mathexp.Series, error) { | 
					
						
							|  |  |  | 	tsSchema := frame.TimeSeriesSchema() | 
					
						
							|  |  |  | 	if tsSchema.Type != data.TimeSeriesTypeWide { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("input data must be a wide series but got type %s (input refid)", tsSchema.Type) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(tsSchema.ValueIndices) == 1 { | 
					
						
							|  |  |  | 		s, err := mathexp.SeriesFromFrame(frame) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return []mathexp.Series{s}, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	series := []mathexp.Series{} | 
					
						
							|  |  |  | 	for _, valIdx := range tsSchema.ValueIndices { | 
					
						
							|  |  |  | 		l := frame.Rows() | 
					
						
							|  |  |  | 		f := data.NewFrameOfFieldTypes(frame.Name, l, frame.Fields[tsSchema.TimeIndex].Type(), frame.Fields[valIdx].Type()) | 
					
						
							|  |  |  | 		f.Fields[0].Name = frame.Fields[tsSchema.TimeIndex].Name | 
					
						
							|  |  |  | 		f.Fields[1].Name = frame.Fields[valIdx].Name | 
					
						
							| 
									
										
										
										
											2022-03-10 23:03:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// The new value fields' configs gets pointed to the one in the original frame
 | 
					
						
							|  |  |  | 		f.Fields[1].Config = frame.Fields[valIdx].Config | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 20:17:00 +08:00
										 |  |  | 		if frame.Fields[valIdx].Labels != nil { | 
					
						
							|  |  |  | 			f.Fields[1].Labels = frame.Fields[valIdx].Labels.Copy() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for i := 0; i < l; i++ { | 
					
						
							|  |  |  | 			f.SetRow(i, frame.Fields[tsSchema.TimeIndex].CopyAt(i), frame.Fields[valIdx].CopyAt(i)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		s, err := mathexp.SeriesFromFrame(f) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		series = append(series, s) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return series, nil | 
					
						
							|  |  |  | } |