package eval import ( "fmt" "sort" "strings" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/expr/classic" ) func extractEvalString(frame *data.Frame) (s string) { if frame == nil { return "empty frame" } if frame.Meta == nil || frame.Meta.Custom == nil { return } if evalMatches, ok := frame.Meta.Custom.([]classic.EvalMatch); ok { sb := strings.Builder{} for i, m := range evalMatches { sb.WriteString("[ ") sb.WriteString(fmt.Sprintf("var='%s%v' ", frame.RefID, i)) sb.WriteString(fmt.Sprintf("metric='%s' ", m.Metric)) sb.WriteString(fmt.Sprintf("labels={%s} ", m.Labels)) valString := "null" if m.Value != nil { valString = fmt.Sprintf("%v", *m.Value) } sb.WriteString(fmt.Sprintf("value=%v ", valString)) sb.WriteString("]") if i < len(evalMatches)-1 { sb.WriteString(", ") } } return sb.String() } if captures, ok := frame.Meta.Custom.([]NumberValueCapture); ok { // Sort captures in ascending order of "Var" so we can assert in tests sort.Slice(captures, func(i, j int) bool { return captures[i].Var < captures[j].Var }) sb := strings.Builder{} for i, capture := range captures { sb.WriteString("[ ") sb.WriteString(fmt.Sprintf("var='%s' ", capture.Var)) sb.WriteString(fmt.Sprintf("labels={%s} ", capture.Labels)) valString := "null" if capture.Value != nil { valString = fmt.Sprintf("%v", *capture.Value) } sb.WriteString(fmt.Sprintf("value=%v ", valString)) sb.WriteString("]") if i < len(captures)-1 { sb.WriteString(", ") } } return sb.String() } return "" } // extractValues returns the RefID and value for all classic conditions, reduce, and math expressions in the frame. // For classic conditions the same refID can have multiple values due to multiple conditions, for them we use the index of // the condition in addition to the refID to distinguish between different values. // It returns nil if there are no results in the frame. func extractValues(frame *data.Frame) map[string]NumberValueCapture { if frame == nil { return nil } if frame.Meta == nil || frame.Meta.Custom == nil { return nil } if matches, ok := frame.Meta.Custom.([]classic.EvalMatch); ok { // Classic evaluations only have a single match but it can contain multiple conditions. // Conditions have a strict ordering which we can rely on to distinguish between values. v := make(map[string]NumberValueCapture, len(matches)) for i, match := range matches { // In classic conditions we use refID and the condition position as a way to distinguish between values. // We can guarantee determinism as conditions are ordered and this order is preserved when marshaling. refID := fmt.Sprintf("%s%d", frame.RefID, i) v[refID] = NumberValueCapture{ Var: frame.RefID, Labels: match.Labels, Value: match.Value, } } return v } if captures, ok := frame.Meta.Custom.([]NumberValueCapture); ok { v := make(map[string]NumberValueCapture, len(captures)) for _, capture := range captures { v[capture.Var] = capture } return v } return nil }