Expressions: Use enumerations rather than strings (#83741)

This commit is contained in:
Ryan McKinley 2024-03-01 09:38:32 -08:00 committed by GitHub
parent c59ebfc60f
commit 5f6bf93dd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 77 additions and 41 deletions

View File

@ -54,7 +54,7 @@ type condition struct {
// Operator is the logical operator to use when there are two conditions in ConditionsCmd. // Operator is the logical operator to use when there are two conditions in ConditionsCmd.
// If there are more than two conditions in ConditionsCmd then operator is used to compare // If there are more than two conditions in ConditionsCmd then operator is used to compare
// the outcome of this condition with that of the condition before it. // the outcome of this condition with that of the condition before it.
Operator string Operator ConditionOperatorType
} }
// NeedsVars returns the variable names (refIds) that are dependencies // NeedsVars returns the variable names (refIds) that are dependencies
@ -216,7 +216,7 @@ func (cmd *ConditionsCmd) executeCond(_ context.Context, _ time.Time, cond condi
return isCondFiring, isCondNoData, matches, nil return isCondFiring, isCondNoData, matches, nil
} }
func compareWithOperator(b1, b2 bool, operator string) bool { func compareWithOperator(b1, b2 bool, operator ConditionOperatorType) bool {
if operator == "or" { if operator == "or" {
return b1 || b2 return b1 || b2
} else { } else {
@ -262,8 +262,17 @@ type ConditionEvalJSON struct {
Type string `json:"type"` // e.g. "gt" Type string `json:"type"` // e.g. "gt"
} }
// The reducer function
// +enum
type ConditionOperatorType string
const (
ConditionOperatorAnd ConditionOperatorType = "and"
ConditionOperatorOr ConditionOperatorType = "or"
)
type ConditionOperatorJSON struct { type ConditionOperatorJSON struct {
Type string `json:"type"` Type ConditionOperatorType `json:"type"`
} }
type ConditionQueryJSON struct { type ConditionQueryJSON struct {

View File

@ -205,14 +205,14 @@ func (gr *ReduceCommand) Execute(ctx context.Context, _ time.Time, vars mathexp.
type ResampleCommand struct { type ResampleCommand struct {
Window time.Duration Window time.Duration
VarToResample string VarToResample string
Downsampler string Downsampler mathexp.ReducerID
Upsampler string Upsampler mathexp.Upsampler
TimeRange TimeRange TimeRange TimeRange
refID string refID string
} }
// NewResampleCommand creates a new ResampleCMD. // NewResampleCommand creates a new ResampleCMD.
func NewResampleCommand(refID, rawWindow, varToResample string, downsampler string, upsampler string, tr TimeRange) (*ResampleCommand, error) { func NewResampleCommand(refID, rawWindow, varToResample string, downsampler mathexp.ReducerID, upsampler mathexp.Upsampler, tr TimeRange) (*ResampleCommand, error) {
// TODO: validate reducer here, before execution // TODO: validate reducer here, before execution
window, err := gtime.ParseDuration(rawWindow) window, err := gtime.ParseDuration(rawWindow)
if err != nil { if err != nil {
@ -271,7 +271,11 @@ func UnmarshalResampleCommand(rn *rawNode) (*ResampleCommand, error) {
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T", upsampler) return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T", upsampler)
} }
return NewResampleCommand(rn.RefID, window, varToResample, downsampler, upsampler, rn.TimeRange) return NewResampleCommand(rn.RefID, window,
varToResample,
mathexp.ReducerID(downsampler),
mathexp.Upsampler(upsampler),
rn.TimeRange)
} }
// NeedsVars returns the variable names (refIds) that are dependencies // NeedsVars returns the variable names (refIds) that are dependencies

View File

@ -7,8 +7,23 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
) )
// The upsample function
// +enum
type Upsampler string
const (
// Use the last seen value
UpsamplerPad Upsampler = "pad"
// backfill
UpsamplerBackfill Upsampler = "backfilling"
// Do not fill values (nill)
UpsamplerFillNA Upsampler = "fillna"
)
// Resample turns the Series into a Number based on the given reduction function // Resample turns the Series into a Number based on the given reduction function
func (s Series) Resample(refID string, interval time.Duration, downsampler string, upsampler string, from, to time.Time) (Series, error) { func (s Series) Resample(refID string, interval time.Duration, downsampler ReducerID, upsampler Upsampler, from, to time.Time) (Series, error) {
newSeriesLength := int(float64(to.Sub(from).Nanoseconds()) / float64(interval.Nanoseconds())) newSeriesLength := int(float64(to.Sub(from).Nanoseconds()) / float64(interval.Nanoseconds()))
if newSeriesLength <= 0 { if newSeriesLength <= 0 {
return s, fmt.Errorf("the series cannot be sampled further; the time range is shorter than the interval") return s, fmt.Errorf("the series cannot be sampled further; the time range is shorter than the interval")
@ -37,19 +52,19 @@ func (s Series) Resample(refID string, interval time.Duration, downsampler strin
var value *float64 var value *float64
if len(vals) == 0 { // upsampling if len(vals) == 0 { // upsampling
switch upsampler { switch upsampler {
case "pad": case UpsamplerPad:
if lastSeen != nil { if lastSeen != nil {
value = lastSeen value = lastSeen
} else { } else {
value = nil value = nil
} }
case "backfilling": case UpsamplerBackfill:
if sIdx == s.Len() { // no vals left if sIdx == s.Len() { // no vals left
value = nil value = nil
} else { } else {
_, value = s.GetPoint(sIdx) _, value = s.GetPoint(sIdx)
} }
case "fillna": case UpsamplerFillNA:
value = nil value = nil
default: default:
return s, fmt.Errorf("upsampling %v not implemented", upsampler) return s, fmt.Errorf("upsampling %v not implemented", upsampler)
@ -61,15 +76,15 @@ func (s Series) Resample(refID string, interval time.Duration, downsampler strin
ff := Float64Field(*fVec) ff := Float64Field(*fVec)
var tmp *float64 var tmp *float64
switch downsampler { switch downsampler {
case "sum": case ReducerSum:
tmp = Sum(&ff) tmp = Sum(&ff)
case "mean": case ReducerMean:
tmp = Avg(&ff) tmp = Avg(&ff)
case "min": case ReducerMin:
tmp = Min(&ff) tmp = Min(&ff)
case "max": case ReducerMax:
tmp = Max(&ff) tmp = Max(&ff)
case "last": case ReducerLast:
tmp = Last(&ff) tmp = Last(&ff)
default: default:
return s, fmt.Errorf("downsampling %v not implemented", downsampler) return s, fmt.Errorf("downsampling %v not implemented", downsampler)

View File

@ -13,8 +13,8 @@ func TestResampleSeries(t *testing.T) {
var tests = []struct { var tests = []struct {
name string name string
interval time.Duration interval time.Duration
downsampler string downsampler ReducerID
upsampler string upsampler Upsampler
timeRange backend.TimeRange timeRange backend.TimeRange
seriesToResample Series seriesToResample Series
series Series series Series

View File

@ -54,10 +54,10 @@ type ResampleQuery struct {
Window string `json:"window" jsonschema:"minLength=1,example=1w,example=10m"` Window string `json:"window" jsonschema:"minLength=1,example=1w,example=10m"`
// The downsample function // The downsample function
Downsampler string `json:"downsampler"` Downsampler mathexp.ReducerID `json:"downsampler"`
// The upsample function // The upsample function
Upsampler string `json:"upsampler"` Upsampler mathexp.Upsampler `json:"upsampler"`
} }
type ThresholdQuery struct { type ThresholdQuery struct {

View File

@ -156,7 +156,7 @@ func (h *ExpressionQueryReader) ReadQuery(
} }
func getReferenceVar(exp string, refId string) (string, error) { func getReferenceVar(exp string, refId string) (string, error) {
exp = strings.TrimPrefix(exp, "%") exp = strings.TrimPrefix(exp, "$")
if exp == "" { if exp == "" {
return "", fmt.Errorf("no variable specified to reference for refId %v", refId) return "", fmt.Errorf("no variable specified to reference for refId %v", refId)
} }

View File

@ -18,23 +18,31 @@ import (
type ThresholdCommand struct { type ThresholdCommand struct {
ReferenceVar string ReferenceVar string
RefID string RefID string
ThresholdFunc string ThresholdFunc ThresholdType
Conditions []float64 Conditions []float64
Invert bool Invert bool
} }
// +enum
type ThresholdType string
const ( const (
ThresholdIsAbove = "gt" ThresholdIsAbove ThresholdType = "gt"
ThresholdIsBelow = "lt" ThresholdIsBelow ThresholdType = "lt"
ThresholdIsWithinRange = "within_range" ThresholdIsWithinRange ThresholdType = "within_range"
ThresholdIsOutsideRange = "outside_range" ThresholdIsOutsideRange ThresholdType = "outside_range"
) )
var ( var (
supportedThresholdFuncs = []string{ThresholdIsAbove, ThresholdIsBelow, ThresholdIsWithinRange, ThresholdIsOutsideRange} supportedThresholdFuncs = []string{
string(ThresholdIsAbove),
string(ThresholdIsBelow),
string(ThresholdIsWithinRange),
string(ThresholdIsOutsideRange),
}
) )
func NewThresholdCommand(refID, referenceVar, thresholdFunc string, conditions []float64) (*ThresholdCommand, error) { func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType, conditions []float64) (*ThresholdCommand, error) {
switch thresholdFunc { switch thresholdFunc {
case ThresholdIsOutsideRange, ThresholdIsWithinRange: case ThresholdIsOutsideRange, ThresholdIsWithinRange:
if len(conditions) < 2 { if len(conditions) < 2 {
@ -57,8 +65,8 @@ func NewThresholdCommand(refID, referenceVar, thresholdFunc string, conditions [
} }
type ConditionEvalJSON struct { type ConditionEvalJSON struct {
Params []float64 `json:"params"` Params []float64 `json:"params"`
Type string `json:"type"` // e.g. "gt" Type ThresholdType `json:"type"` // e.g. "gt"
} }
// UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query. // UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query.
@ -121,7 +129,7 @@ func (tc *ThresholdCommand) Execute(ctx context.Context, now time.Time, vars mat
} }
// createMathExpression converts all the info we have about a "threshold" expression in to a Math expression // createMathExpression converts all the info we have about a "threshold" expression in to a Math expression
func createMathExpression(referenceVar string, thresholdFunc string, args []float64, invert bool) (string, error) { func createMathExpression(referenceVar string, thresholdFunc ThresholdType, args []float64, invert bool) (string, error) {
var exp string var exp string
switch thresholdFunc { switch thresholdFunc {
case ThresholdIsAbove: case ThresholdIsAbove:

View File

@ -14,7 +14,7 @@ import (
func TestNewThresholdCommand(t *testing.T) { func TestNewThresholdCommand(t *testing.T) {
type testCase struct { type testCase struct {
fn string fn ThresholdType
args []float64 args []float64
shouldError bool shouldError bool
expectedError string expectedError string
@ -107,7 +107,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
require.IsType(t, &ThresholdCommand{}, command) require.IsType(t, &ThresholdCommand{}, command)
cmd := command.(*ThresholdCommand) cmd := command.(*ThresholdCommand)
require.Equal(t, []string{"A"}, cmd.NeedsVars()) require.Equal(t, []string{"A"}, cmd.NeedsVars())
require.Equal(t, "gt", cmd.ThresholdFunc) require.Equal(t, ThresholdIsAbove, cmd.ThresholdFunc)
require.Equal(t, []float64{20.0, 80.0}, cmd.Conditions) require.Equal(t, []float64{20.0, 80.0}, cmd.Conditions)
}, },
}, },
@ -172,10 +172,10 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
cmd := c.(*HysteresisCommand) cmd := c.(*HysteresisCommand)
require.Equal(t, []string{"B"}, cmd.NeedsVars()) require.Equal(t, []string{"B"}, cmd.NeedsVars())
require.Equal(t, []string{"B"}, cmd.LoadingThresholdFunc.NeedsVars()) require.Equal(t, []string{"B"}, cmd.LoadingThresholdFunc.NeedsVars())
require.Equal(t, "gt", cmd.LoadingThresholdFunc.ThresholdFunc) require.Equal(t, ThresholdIsAbove, cmd.LoadingThresholdFunc.ThresholdFunc)
require.Equal(t, []float64{100.0}, cmd.LoadingThresholdFunc.Conditions) require.Equal(t, []float64{100.0}, cmd.LoadingThresholdFunc.Conditions)
require.Equal(t, []string{"B"}, cmd.UnloadingThresholdFunc.NeedsVars()) require.Equal(t, []string{"B"}, cmd.UnloadingThresholdFunc.NeedsVars())
require.Equal(t, "lt", cmd.UnloadingThresholdFunc.ThresholdFunc) require.Equal(t, ThresholdIsBelow, cmd.UnloadingThresholdFunc.ThresholdFunc)
require.Equal(t, []float64{31.0}, cmd.UnloadingThresholdFunc.Conditions) require.Equal(t, []float64{31.0}, cmd.UnloadingThresholdFunc.Conditions)
require.True(t, cmd.UnloadingThresholdFunc.Invert) require.True(t, cmd.UnloadingThresholdFunc.Invert)
require.NotNil(t, cmd.LoadedDimensions) require.NotNil(t, cmd.LoadedDimensions)
@ -233,7 +233,7 @@ func TestCreateMathExpression(t *testing.T) {
expected string expected string
ref string ref string
function string function ThresholdType
params []float64 params []float64
} }
@ -297,7 +297,7 @@ func TestCreateMathExpression(t *testing.T) {
func TestIsSupportedThresholdFunc(t *testing.T) { func TestIsSupportedThresholdFunc(t *testing.T) {
type testCase struct { type testCase struct {
function string function ThresholdType
supported bool supported bool
} }
@ -325,8 +325,8 @@ func TestIsSupportedThresholdFunc(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.function, func(t *testing.T) { t.Run(string(tc.function), func(t *testing.T) {
supported := IsSupportedThresholdFunc(tc.function) supported := IsSupportedThresholdFunc(string(tc.function))
require.Equal(t, supported, tc.supported) require.Equal(t, supported, tc.supported)
}) })
} }

View File

@ -16,8 +16,8 @@ import (
type dataEvaluator struct { type dataEvaluator struct {
refID string refID string
data []mathexp.Series data []mathexp.Series
downsampleFunction string downsampleFunction mathexp.ReducerID
upsampleFunction string upsampleFunction mathexp.Upsampler
} }
func newDataEvaluator(refID string, frame *data.Frame) (*dataEvaluator, error) { func newDataEvaluator(refID string, frame *data.Frame) (*dataEvaluator, error) {