mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 11:52:30 +08:00
SQL Expressions: Make SQL Expressions work with Alerting (#101820)
Initial support for alerting with SQL expressions - When `format` is set to `alerting`, SQL expressions output in a format suitable for alerting evaluation. - Outstanding TODOs: - Deduplicate output rows - Add more tests - Fix broken alerting UI rendering (likely due to shape change to undocumented full-long format) - Basic usage: - SQL must return one numeric column and one or more string columns. - Each row may become an alert. - The alert fires if the numeric value is non-zero. - String columns are treated as labels. --------- Co-authored-by: Konrad Lalik <konradlalik@gmail.com> Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com> Co-authored-by: Sam Jewell <sam.jewell@grafana.com>
This commit is contained in:
@ -30,10 +30,11 @@ type SQLCommand struct {
|
||||
varsToQuery []string
|
||||
refID string
|
||||
limit int64
|
||||
format string
|
||||
}
|
||||
|
||||
// NewSQLCommand creates a new SQLCommand.
|
||||
func NewSQLCommand(refID, rawSQL string, limit int64) (*SQLCommand, error) {
|
||||
func NewSQLCommand(refID, format, rawSQL string, limit int64) (*SQLCommand, error) {
|
||||
if rawSQL == "" {
|
||||
return nil, ErrMissingSQLQuery
|
||||
}
|
||||
@ -62,6 +63,7 @@ func NewSQLCommand(refID, rawSQL string, limit int64) (*SQLCommand, error) {
|
||||
varsToQuery: tables,
|
||||
refID: refID,
|
||||
limit: limit,
|
||||
format: format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -83,7 +85,10 @@ func UnmarshalSQLCommand(rn *rawNode, limit int64) (*SQLCommand, error) {
|
||||
return nil, fmt.Errorf("expected sql expression to be type string, but got type %T", expressionRaw)
|
||||
}
|
||||
|
||||
return NewSQLCommand(rn.RefID, expression, limit)
|
||||
formatRaw := rn.Query["format"]
|
||||
format, _ := formatRaw.(string)
|
||||
|
||||
return NewSQLCommand(rn.RefID, format, expression, limit)
|
||||
}
|
||||
|
||||
// NeedsVars returns the variable names (refIds) that are dependencies
|
||||
@ -140,10 +145,24 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
rsp.Values = mathexp.Values{
|
||||
mathexp.TableData{Frame: frame},
|
||||
}
|
||||
switch gr.format {
|
||||
case "alerting":
|
||||
numberSet, err := extractNumberSetFromSQLForAlerting(frame)
|
||||
if err != nil {
|
||||
rsp.Error = err
|
||||
return rsp, nil
|
||||
}
|
||||
vals := make([]mathexp.Value, 0, len(numberSet))
|
||||
for i := range numberSet {
|
||||
vals = append(vals, numberSet[i])
|
||||
}
|
||||
rsp.Values = vals
|
||||
|
||||
default:
|
||||
rsp.Values = mathexp.Values{
|
||||
mathexp.TableData{Frame: frame},
|
||||
}
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
@ -162,3 +181,57 @@ func totalCells(frames []*data.Frame) (total int64) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func extractNumberSetFromSQLForAlerting(frame *data.Frame) ([]mathexp.Number, error) {
|
||||
var (
|
||||
numericField *data.Field
|
||||
numericFieldIx int
|
||||
)
|
||||
|
||||
// Find the only numeric field
|
||||
for i, f := range frame.Fields {
|
||||
if f.Type().Numeric() {
|
||||
if numericField != nil {
|
||||
return nil, fmt.Errorf("expected exactly one numeric field, but found multiple")
|
||||
}
|
||||
numericField = f
|
||||
numericFieldIx = i
|
||||
}
|
||||
}
|
||||
if numericField == nil {
|
||||
return nil, fmt.Errorf("no numeric field found in frame")
|
||||
}
|
||||
|
||||
numbers := make([]mathexp.Number, frame.Rows())
|
||||
|
||||
for i := 0; i < frame.Rows(); i++ {
|
||||
val, err := numericField.FloatAt(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read numeric value at row %d: %w", i, err)
|
||||
}
|
||||
|
||||
labels := data.Labels{}
|
||||
for j, f := range frame.Fields {
|
||||
if j == numericFieldIx || (f.Type() != data.FieldTypeString && f.Type() != data.FieldTypeNullableString) {
|
||||
continue
|
||||
}
|
||||
|
||||
val := f.At(i)
|
||||
switch v := val.(type) {
|
||||
case *string:
|
||||
if v != nil {
|
||||
labels[f.Name] = *v
|
||||
}
|
||||
case string:
|
||||
labels[f.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
n := mathexp.NewNumber(numericField.Name, labels)
|
||||
n.Frame.Fields[0].Config = numericField.Config
|
||||
n.SetValue(&val)
|
||||
numbers[i] = n
|
||||
}
|
||||
|
||||
return numbers, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user