grafana/pkg/services/ngalert/eval/extract_md.go
George Robinson 89dcaaf049
Alerting: Sort NumberCaptureValues in EvaluationString (#71927)
This commit changes extractEvalString to sort NumberCaptureValues
in ascending order of Var before building the output string. This
means that users will see EvaluationString in a consistent order,
but also make it possible to assert its output in tests.
2023-07-19 12:09:21 +01:00

109 lines
3.1 KiB
Go

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
}