Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions pkg/types/querybuildertypes/querybuildertypesv5/formula.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ func parseAggregationReference(variable string) (aggregationRef, error) {
// Simple query reference like "A" - defaults to first aggregation (index 0)
defaultIndex := 0
return aggregationRef{
QueryName: parts[0],
QueryName: strings.ToUpper(parts[0]),
Index: &defaultIndex,
}, nil
}

if len(parts) == 2 {
queryName := parts[0]
queryName := strings.ToUpper(parts[0])
reference := parts[1]

// Try to parse as index
Expand Down Expand Up @@ -277,10 +277,15 @@ func (fe *FormulaEvaluator) buildSeriesLookup(timeSeriesData map[string]*TimeSer
seriesMetadata: make(map[string]*TimeSeries),
}

normalizedSeriesMap := make(map[string]*TimeSeriesData, len(timeSeriesData))
for name, ts := range timeSeriesData {
normalizedSeriesMap[strings.ToUpper(name)] = ts
}

for variable, aggRef := range fe.aggRefs {
// We are only interested in the time series data for the queries that are
// involved in the formula expression.
data, exists := timeSeriesData[aggRef.QueryName]
data, exists := normalizedSeriesMap[aggRef.QueryName]
if !exists {
continue
}
Expand Down
126 changes: 126 additions & 0 deletions pkg/types/querybuildertypes/querybuildertypesv5/formula_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,132 @@ func TestComplexExpression(t *testing.T) {
}
}

func TestCaseInsensitiveQueryNames(t *testing.T) {
tests := []struct {
name string
expression string
tsData map[string]*TimeSeriesData
expected float64
}{
{
name: "lowercase query names",
expression: "a / b",
tsData: map[string]*TimeSeriesData{
"A": createFormulaTestTimeSeriesData("A", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 10}),
},
}),
"B": createFormulaTestTimeSeriesData("B", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 2}),
},
}),
},
expected: 5.0,
},
{
name: "mixed case query names",
expression: "A / b",
tsData: map[string]*TimeSeriesData{
"A": createFormulaTestTimeSeriesData("A", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 10}),
},
}),
"B": createFormulaTestTimeSeriesData("B", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 2}),
},
}),
},
expected: 5.0,
},
{
name: "uppercase query names with lowercase data keys",
expression: "A / B",
tsData: map[string]*TimeSeriesData{
"a": createFormulaTestTimeSeriesData("a", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 10}),
},
}),
"b": createFormulaTestTimeSeriesData("b", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 2}),
},
}),
},
expected: 5.0,
},
{
name: "all lowercase",
expression: "a/b",
tsData: map[string]*TimeSeriesData{
"a": createFormulaTestTimeSeriesData("a", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 100}),
},
}),
"b": createFormulaTestTimeSeriesData("b", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 10}),
},
}),
},
expected: 10.0,
},
{
name: "complex expression with mixed case",
expression: "a + B * c",
tsData: map[string]*TimeSeriesData{
"A": createFormulaTestTimeSeriesData("A", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 5}),
},
}),
"b": createFormulaTestTimeSeriesData("b", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 3}),
},
}),
"C": createFormulaTestTimeSeriesData("C", []*TimeSeries{
{
Labels: createLabels(map[string]string{}),
Values: createValues(map[int64]float64{1: 2}),
},
}),
},
expected: 11.0, // 5 + 3 * 2 = 11
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
evaluator, err := NewFormulaEvaluator(tt.expression, map[string]bool{"a": true, "A": true, "b": true, "B": true, "c": true, "C": true})
require.NoError(t, err)

result, err := evaluator.EvaluateFormula(tt.tsData)
require.NoError(t, err)
require.NotNil(t, result)

assert.Equal(t, 1, len(result), "should have exactly one result series")
assert.Equal(t, 1, len(result[0].Values), "should have exactly one value")
assert.Equal(t, tt.expected, result[0].Values[0].Value, "calculated value should match expected")
})
}
}

func TestAbsValueExpression(t *testing.T) {
tsData := map[string]*TimeSeriesData{
"A": createFormulaTestTimeSeriesData("A", []*TimeSeries{
Expand Down
Loading