mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			133 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package expr
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/expr/mathexp"
 | |
| )
 | |
| 
 | |
| type ThresholdCommand struct {
 | |
| 	ReferenceVar  string
 | |
| 	RefID         string
 | |
| 	ThresholdFunc string
 | |
| 	Conditions    []float64
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	ThresholdIsAbove        = "gt"
 | |
| 	ThresholdIsBelow        = "lt"
 | |
| 	ThresholdIsWithinRange  = "within_range"
 | |
| 	ThresholdIsOutsideRange = "outside_range"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	supportedThresholdFuncs = []string{ThresholdIsAbove, ThresholdIsBelow, ThresholdIsWithinRange, ThresholdIsOutsideRange}
 | |
| )
 | |
| 
 | |
| func NewThresholdCommand(refID, referenceVar, thresholdFunc string, conditions []float64) (*ThresholdCommand, error) {
 | |
| 	return &ThresholdCommand{
 | |
| 		RefID:         refID,
 | |
| 		ReferenceVar:  referenceVar,
 | |
| 		ThresholdFunc: thresholdFunc,
 | |
| 		Conditions:    conditions,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| type ThresholdConditionJSON struct {
 | |
| 	Evaluator ConditionEvalJSON `json:"evaluator"`
 | |
| }
 | |
| 
 | |
| type ConditionEvalJSON struct {
 | |
| 	Params []float64 `json:"params"`
 | |
| 	Type   string    `json:"type"` // e.g. "gt"
 | |
| }
 | |
| 
 | |
| // UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query.
 | |
| func UnmarshalThresholdCommand(rn *rawNode) (*ThresholdCommand, error) {
 | |
| 	rawQuery := rn.Query
 | |
| 
 | |
| 	rawExpression, ok := rawQuery["expression"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no variable specified to reference for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	referenceVar, ok := rawExpression.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected threshold variable to be a string, got %T for refId %v", rawExpression, rn.RefID)
 | |
| 	}
 | |
| 
 | |
| 	jsonFromM, err := json.Marshal(rawQuery["conditions"])
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to remarshal threshold expression body: %w", err)
 | |
| 	}
 | |
| 	var conditions []ThresholdConditionJSON
 | |
| 	if err = json.Unmarshal(jsonFromM, &conditions); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to unmarshal remarshaled threshold expression body: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	for _, condition := range conditions {
 | |
| 		if !IsSupportedThresholdFunc(condition.Evaluator.Type) {
 | |
| 			return nil, fmt.Errorf("expected threshold function to be one of %s, got %s", strings.Join(supportedThresholdFuncs, ", "), condition.Evaluator.Type)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// we only support one condition for now, we might want to turn this in to "OR" expressions later
 | |
| 	if len(conditions) != 1 {
 | |
| 		return nil, fmt.Errorf("threshold expression requires exactly one condition")
 | |
| 	}
 | |
| 	firstCondition := conditions[0]
 | |
| 
 | |
| 	return NewThresholdCommand(rn.RefID, referenceVar, firstCondition.Evaluator.Type, firstCondition.Evaluator.Params)
 | |
| }
 | |
| 
 | |
| // NeedsVars returns the variable names (refIds) that are dependencies
 | |
| // to execute the command and allows the command to fulfill the Command interface.
 | |
| func (tc *ThresholdCommand) NeedsVars() []string {
 | |
| 	return []string{tc.ReferenceVar}
 | |
| }
 | |
| 
 | |
| func (tc *ThresholdCommand) Execute(ctx context.Context, now time.Time, vars mathexp.Vars) (mathexp.Results, error) {
 | |
| 	mathExpression, err := createMathExpression(tc.ReferenceVar, tc.ThresholdFunc, tc.Conditions)
 | |
| 	if err != nil {
 | |
| 		return mathexp.Results{}, err
 | |
| 	}
 | |
| 
 | |
| 	mathCommand, err := NewMathCommand(tc.ReferenceVar, mathExpression)
 | |
| 	if err != nil {
 | |
| 		return mathexp.Results{}, err
 | |
| 	}
 | |
| 
 | |
| 	return mathCommand.Execute(ctx, now, vars)
 | |
| }
 | |
| 
 | |
| // createMathExpression converts all the info we have about a "threshold" expression in to a Math expression
 | |
| func createMathExpression(referenceVar string, thresholdFunc string, args []float64) (string, error) {
 | |
| 	switch thresholdFunc {
 | |
| 	case ThresholdIsAbove:
 | |
| 		return fmt.Sprintf("${%s} > %f", referenceVar, args[0]), nil
 | |
| 	case ThresholdIsBelow:
 | |
| 		return fmt.Sprintf("${%s} < %f", referenceVar, args[0]), nil
 | |
| 	case ThresholdIsWithinRange:
 | |
| 		return fmt.Sprintf("${%s} > %f && ${%s} < %f", referenceVar, args[0], referenceVar, args[1]), nil
 | |
| 	case ThresholdIsOutsideRange:
 | |
| 		return fmt.Sprintf("${%s} < %f || ${%s} > %f", referenceVar, args[0], referenceVar, args[1]), nil
 | |
| 	default:
 | |
| 		return "", fmt.Errorf("failed to evaluate threshold expression: no such threshold function %s", thresholdFunc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func IsSupportedThresholdFunc(name string) bool {
 | |
| 	isSupported := false
 | |
| 
 | |
| 	for _, funcName := range supportedThresholdFuncs {
 | |
| 		if funcName == name {
 | |
| 			isSupported = true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return isSupported
 | |
| }
 |