Alerting: Add frames for all queries and expressions (#55609)

This commit is one of two commits to make the data frames for all queries and expressions in an alert rule available to the state package for rendering a graph. It renames Result to Condition, and creates an additional field called
Results that is a map of Ref ID to data.Frames.
This commit is contained in:
George Robinson
2022-09-27 10:05:29 +01:00
committed by GitHub
parent 4544494058
commit a49fcbdbbc
2 changed files with 38 additions and 29 deletions
+24 -15
View File
@@ -76,12 +76,16 @@ func (e *invalidEvalResultFormatError) Unwrap() error {
// ExecutionResults contains the unevaluated results from executing
// a condition.
type ExecutionResults struct {
Error error
// Condition contains the results of the condition
Condition data.Frames
// Results contains the results of all queries, reduce and math expressions
Results map[string]data.Frames
// NoData contains the DatasourceUID for RefIDs that returned no data.
NoData map[string]string
Results data.Frames
Error error
}
// Results is a slice of evaluated alert instances states.
@@ -101,20 +105,24 @@ func (evalResults Results) HasErrors() bool {
type Result struct {
Instance data.Labels
State State // Enum
// Error message for Error state. should be nil if State != Error.
Error error
EvaluatedAt time.Time
EvaluationDuration time.Duration
// EvaluationString is a string representation of evaluation data such
// as EvalMatches (from "classic condition"), and in the future from operations
// like SSE "math".
EvaluationString string
// Error message for Error state. should be nil if State != Error.
Error error
// Results contains the results of all queries, reduce and math expressions
Results map[string]data.Frames
// Values contains the RefID and value of reduce and math expressions.
// It does not contain values for classic conditions as the values
// in classic conditions do not have a RefID.
Values map[string]NumberValueCapture
EvaluatedAt time.Time
EvaluationDuration time.Duration
// EvaluationString is a string representation of evaluation data such
// as EvalMatches (from "classic condition"), and in the future from operations
// like SSE "math".
EvaluationString string
}
// State is an enum of the evaluation State for an alert instance.
@@ -243,7 +251,7 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q
// datasourceExprUID is a special DatasourceUID for expressions
datasourceExprUID := strconv.FormatInt(expr.DatasourceID, 10)
var result ExecutionResults
result := ExecutionResults{Results: make(map[string]data.Frames)}
for refID, res := range execResp.Responses {
if len(res.Frames) == 0 {
// to ensure that NoData is consistent with Results we do not initialize NoData
@@ -270,12 +278,13 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q
}
if refID == c.Condition {
result.Results = res.Frames
result.Condition = res.Frames
}
result.Results[refID] = res.Frames
}
// add capture values as data frame metadata to each result (frame) that has matching labels.
for _, frame := range result.Results {
for _, frame := range result.Condition {
// classic conditions already have metadata set and only have one value, there's no need to add anything in this case.
if frame.Meta != nil && frame.Meta.Custom != nil {
if _, ok := frame.Meta.Custom.([]classic.EvalMatch); ok {
@@ -422,12 +431,12 @@ func evaluateExecutionResult(execResults ExecutionResults, ts time.Time) Results
return evalResults
}
if len(execResults.Results) == 0 {
if len(execResults.Condition) == 0 {
appendNoData(nil)
return evalResults
}
for _, f := range execResults.Results {
for _, f := range execResults.Condition {
rowLen, err := f.RowLen()
if err != nil {
appendErrRes(&invalidEvalResultFormatError{refID: f.RefID, reason: "unable to get frame row length", err: err})
+14 -14
View File
@@ -30,7 +30,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "zero valued single instance is single Normal state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(0)})),
},
},
@@ -44,7 +44,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "non-zero valued single instance is single Alerting state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(1)})),
},
},
@@ -58,7 +58,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "nil value single instance is single a NoData state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("", data.NewField("", nil, []*float64{nil})),
},
},
@@ -95,7 +95,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "frame with no fields produces a NoData state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame(""),
},
},
@@ -109,7 +109,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "empty field produces a NoData state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("", data.NewField("", nil, []*float64{})),
},
},
@@ -123,7 +123,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "empty field with labels produces a NoData state result with labels",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("", data.NewField("", data.Labels{"a": "b"}, []*float64{})),
},
},
@@ -138,7 +138,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "malformed frame (unequal lengths) produces Error state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []*float64{ptr.Float64(23)}),
data.NewField("", nil, []*float64{}),
@@ -156,7 +156,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "too many fields produces Error state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []*float64{}),
data.NewField("", nil, []*float64{}),
@@ -174,7 +174,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "more than one row produces Error state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []*float64{ptr.Float64(2), ptr.Float64(3)}),
),
@@ -191,7 +191,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "time fields (looks like time series) returns error",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []time.Time{}),
),
@@ -208,7 +208,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "non []*float64 field will produce Error state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []float64{2}),
),
@@ -225,7 +225,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "duplicate labels produce a single Error state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []*float64{ptr.Float64(1)}),
),
@@ -245,7 +245,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "error that produce duplicate empty labels produce a single Error state result",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", data.Labels{"a": "b"}, []float64{2}),
),
@@ -265,7 +265,7 @@ func TestEvaluateExecutionResult(t *testing.T) {
{
desc: "certain errors will produce multiple mixed Error and other state results",
execResults: ExecutionResults{
Results: []*data.Frame{
Condition: []*data.Frame{
data.NewFrame("",
data.NewField("", nil, []float64{3}),
),