diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go index 106eaea318..fcf6944234 100644 --- a/pkg/query-service/app/parser.go +++ b/pkg/query-service/app/parser.go @@ -1142,25 +1142,7 @@ func ParseQueryRangeParams(r *http.Request) (*v3.QueryRangeParamsV3, *model.ApiE } } - // Remove the time shift function from the list of functions and set the shift by value - var timeShiftBy int64 - if len(query.Functions) > 0 { - for idx := range query.Functions { - function := &query.Functions[idx] - if function.Name == v3.FunctionNameTimeShift { - // move the function to the beginning of the list - // so any other function can use the shifted time - var fns []v3.Function - fns = append(fns, *function) - fns = append(fns, query.Functions[:idx]...) - fns = append(fns, query.Functions[idx+1:]...) - query.Functions = fns - timeShiftBy = int64(function.Args[0].(float64)) - break - } - } - } - query.ShiftBy = timeShiftBy + query.SetShiftByFromFunc() if query.Filters == nil || len(query.Filters.Items) == 0 { continue diff --git a/pkg/query-service/app/parser_test.go b/pkg/query-service/app/parser_test.go index a5367b70ff..ed290d1355 100644 --- a/pkg/query-service/app/parser_test.go +++ b/pkg/query-service/app/parser_test.go @@ -297,6 +297,8 @@ func TestParseQueryRangeParamsCompositeQuery(t *testing.T) { compositeQuery v3.CompositeQuery expectErr bool errMsg string + hasShiftBy bool + shiftBy int64 }{ { desc: "no query in request", @@ -496,6 +498,56 @@ func TestParseQueryRangeParamsCompositeQuery(t *testing.T) { expectErr: true, errMsg: "builder query A is invalid: group by is invalid", }, + { + desc: "builder query with shift by", + compositeQuery: v3.CompositeQuery{ + PanelType: v3.PanelTypeGraph, + QueryType: v3.QueryTypeBuilder, + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + DataSource: "logs", + AggregateOperator: "sum", + AggregateAttribute: v3.AttributeKey{Key: "attribute"}, + GroupBy: []v3.AttributeKey{{Key: "group_key"}}, + Expression: "A", + Functions: []v3.Function{ + { + Name: v3.FunctionNameTimeShift, + Args: []interface{}{float64(10)}, + }, + }, + }, + }, + }, + hasShiftBy: true, + shiftBy: 10, + }, + { + desc: "builder query with shift by as string", + compositeQuery: v3.CompositeQuery{ + PanelType: v3.PanelTypeGraph, + QueryType: v3.QueryTypeBuilder, + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + DataSource: "logs", + AggregateOperator: "sum", + AggregateAttribute: v3.AttributeKey{Key: "attribute"}, + GroupBy: []v3.AttributeKey{{Key: "group_key"}}, + Expression: "A", + Functions: []v3.Function{ + { + Name: v3.FunctionNameTimeShift, + Args: []interface{}{"3600"}, + }, + }, + }, + }, + }, + hasShiftBy: true, + shiftBy: 3600, + }, } for _, tc := range reqCases { @@ -514,13 +566,16 @@ func TestParseQueryRangeParamsCompositeQuery(t *testing.T) { require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/api/v3/query_range", body) - _, apiErr := ParseQueryRangeParams(req) + params, apiErr := ParseQueryRangeParams(req) if tc.expectErr { require.Error(t, apiErr) require.Contains(t, apiErr.Error(), tc.errMsg) } else { require.Nil(t, apiErr) } + if tc.hasShiftBy { + require.Equal(t, tc.shiftBy, params.CompositeQuery.BuilderQueries["A"].ShiftBy) + } }) } } diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 550fc65f5f..c6bee702c0 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" + "go.uber.org/zap" ) type DataSource string @@ -789,6 +790,38 @@ type BuilderQuery struct { QueriesUsedInFormula []string } +func (b *BuilderQuery) SetShiftByFromFunc() { + // Remove the time shift function from the list of functions and set the shift by value + var timeShiftBy int64 + if len(b.Functions) > 0 { + for idx := range b.Functions { + function := &b.Functions[idx] + if function.Name == FunctionNameTimeShift { + // move the function to the beginning of the list + // so any other function can use the shifted time + var fns []Function + fns = append(fns, *function) + fns = append(fns, b.Functions[:idx]...) + fns = append(fns, b.Functions[idx+1:]...) + b.Functions = fns + if len(function.Args) > 0 { + if shift, ok := function.Args[0].(float64); ok { + timeShiftBy = int64(shift) + } else if shift, ok := function.Args[0].(string); ok { + shiftBy, err := strconv.ParseFloat(shift, 64) + if err != nil { + zap.L().Error("failed to parse time shift by", zap.String("shift", shift), zap.Error(err)) + } + timeShiftBy = int64(shiftBy) + } + } + break + } + } + } + b.ShiftBy = timeShiftBy +} + func (b *BuilderQuery) Clone() *BuilderQuery { if b == nil { return nil diff --git a/pkg/query-service/rules/threshold_rule.go b/pkg/query-service/rules/threshold_rule.go index 7cc823bb53..3971597ec2 100644 --- a/pkg/query-service/rules/threshold_rule.go +++ b/pkg/query-service/rules/threshold_rule.go @@ -152,6 +152,9 @@ func (r *ThresholdRule) prepareQueryRange(ts time.Time) (*v3.QueryRangeParamsV3, if minStep := common.MinAllowedStepInterval(start, end); q.StepInterval < minStep { q.StepInterval = minStep } + + q.SetShiftByFromFunc() + if q.DataSource == v3.DataSourceMetrics && constants.UseMetricsPreAggregation() { // if the time range is greater than 1 day, and less than 1 week set the step interval to be multiple of 5 minutes // if the time range is greater than 1 week, set the step interval to be multiple of 30 mins diff --git a/pkg/query-service/rules/threshold_rule_test.go b/pkg/query-service/rules/threshold_rule_test.go index d3d84f06a7..e75c82b1a0 100644 --- a/pkg/query-service/rules/threshold_rule_test.go +++ b/pkg/query-service/rules/threshold_rule_test.go @@ -1602,3 +1602,66 @@ func TestThresholdRuleLogsLink(t *testing.T) { } } } + +func TestThresholdRuleShiftBy(t *testing.T) { + target := float64(10) + postableRule := PostableRule{ + AlertName: "Logs link test", + AlertType: AlertTypeLogs, + RuleType: RuleTypeThreshold, + EvalWindow: Duration(5 * time.Minute), + Frequency: Duration(1 * time.Minute), + RuleCondition: &RuleCondition{ + CompositeQuery: &v3.CompositeQuery{ + QueryType: v3.QueryTypeBuilder, + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + StepInterval: 60, + AggregateAttribute: v3.AttributeKey{ + Key: "component", + }, + AggregateOperator: v3.AggregateOperatorCountDistinct, + DataSource: v3.DataSourceLogs, + Expression: "A", + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{ + { + Key: v3.AttributeKey{Key: "k8s.container.name", IsColumn: false, Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeString}, + Value: "testcontainer", + Operator: v3.FilterOperatorEqual, + }, + }, + }, + Functions: []v3.Function{ + { + Name: v3.FunctionNameTimeShift, + Args: []interface{}{float64(10)}, + }, + }, + }, + }, + }, + Target: &target, + CompareOp: ValueAboveOrEq, + }, + } + + rule, err := NewThresholdRule("69", &postableRule, nil, nil, true) + if err != nil { + assert.NoError(t, err) + } + rule.TemporalityMap = map[string]map[v3.Temporality]bool{ + "signoz_calls_total": { + v3.Delta: true, + }, + } + + params, err := rule.prepareQueryRange(time.Now()) + if err != nil { + assert.NoError(t, err) + } + + assert.Equal(t, int64(10), params.CompositeQuery.BuilderQueries["A"].ShiftBy) +}