@@ -5,6 +5,7 @@ package service
55
66import (
77 "context"
8+ "fmt"
89 "testing"
910
1011 "github.com/coze-dev/coze-loop/backend/modules/observability/domain/component/config"
@@ -17,6 +18,7 @@ import (
1718 traceServicemocks "github.com/coze-dev/coze-loop/backend/modules/observability/domain/trace/service/mocks"
1819 spanfilter "github.com/coze-dev/coze-loop/backend/modules/observability/domain/trace/service/trace/span_filter"
1920 spanfiltermocks "github.com/coze-dev/coze-loop/backend/modules/observability/domain/trace/service/trace/span_filter/mocks"
21+ "github.com/coze-dev/coze-loop/backend/pkg/json"
2022 "github.com/stretchr/testify/assert"
2123 "go.uber.org/mock/gomock"
2224)
@@ -642,3 +644,129 @@ func TestGetMetricGroupBy(t *testing.T) {
642644 assert .Contains (t , err .Error (), "groupby dimension has no alias" )
643645 })
644646}
647+
648+ func TestQueryMetrics_GroupBySpaceID_SkipSpaceIds (t * testing.T ) {
649+ t .Parallel ()
650+ ctrl := gomock .NewController (t )
651+ defer ctrl .Finish ()
652+
653+ repoMock := repomocks .NewMockIMetricRepo (ctrl )
654+ tenantMock := tenantmocks .NewMockITenantProvider (ctrl )
655+ builderMock := traceServicemocks .NewMockTraceFilterProcessorBuilder (ctrl )
656+ filterMock := spanfiltermocks .NewMockFilter (ctrl )
657+ traceConfigMock := configmocks .NewMockITraceConfig (ctrl )
658+
659+ // Setup mocks
660+ tenantMock .EXPECT ().GetMetricTenantsByPlatformType (gomock .Any (), gomock .Any ()).Return ([]string {"tenant-1" }, nil )
661+ builderMock .EXPECT ().BuildPlatformRelatedFilter (gomock .Any (), gomock .Any ()).Return (filterMock , nil )
662+
663+ // BuildBasicSpanFilter returns filter with SpaceId=0
664+ filterMock .EXPECT ().BuildBasicSpanFilter (gomock .Any (), gomock .Any ()).DoAndReturn (
665+ func (_ context.Context , env * spanfilter.SpanEnv ) ([]* loop_span.FilterField , bool , error ) {
666+ return []* loop_span.FilterField {
667+ {
668+ FieldName : loop_span .SpanFieldSpaceId ,
669+ Values : []string {"0" },
670+ },
671+ }, true , nil
672+ },
673+ )
674+
675+ // TraceConfig returns SkipSpaceIds
676+ traceConfigMock .EXPECT ().GetMetricOfflineCalculateConfig (gomock .Any ()).Return (config.MetricOfflineCalculateConfig {
677+ SkipSpaceIds : []string {"space_skip_1" , "space_skip_2" },
678+ })
679+ traceConfigMock .EXPECT ().GetMetricQueryConfig (gomock .Any ()).Return (& config.MetricQueryConfig {
680+ SupportOffline : false ,
681+ }).AnyTimes ()
682+
683+ requestFilter := & loop_span.FilterFields {
684+ FilterFields : []* loop_span.FilterField {
685+ {FieldName : "some_req_field" , Values : []string {"val" }},
686+ },
687+ }
688+
689+ // Assert that filters are modified
690+ repoMock .EXPECT ().GetMetrics (gomock .Any (), gomock .Any ()).DoAndReturn (
691+ func (_ context.Context , param * repo.GetMetricsParam ) (* repo.GetMetricsResult , error ) {
692+ d , _ := json .Marshal (param .Filters )
693+ fmt .Println (string (d ))
694+ assert .NotNil (t , param .Filters )
695+ assert .Equal (t , loop_span .QueryAndOrEnumAnd , * param .Filters .QueryAndOr )
696+ // Should have 2 fields: space_id not in, and requestFilter
697+ assert .Len (t , param .Filters .FilterFields , 2 )
698+
699+ // Check first field: space_id not in skip_ids
700+ f1 := param .Filters .FilterFields [0 ]
701+ assert .Equal (t , loop_span .SpanFieldSpaceId , f1 .FieldName )
702+ assert .NotNil (t , f1 .QueryType )
703+ assert .Equal (t , loop_span .QueryTypeEnumNotIn , * f1 .QueryType )
704+ assert .ElementsMatch (t , []string {"space_skip_1" , "space_skip_2" }, f1 .Values )
705+
706+ // Check second field: original filters (basic + request)
707+ f2 := param .Filters .FilterFields [1 ]
708+ assert .Equal (t , loop_span .QueryAndOrEnumAnd , * f2 .QueryAndOr )
709+
710+ // Verify that original filters are preserved
711+ originalFilters := f2 .SubFilter
712+ assert .NotNil (t , originalFilters )
713+ assert .Len (t , originalFilters .FilterFields , 2 )
714+
715+ // 1. Basic filter (should contain space_id which was modified to Exist)
716+ basicWrapper := originalFilters .FilterFields [0 ]
717+ assert .NotNil (t , basicWrapper .SubFilter )
718+
719+ foundSpaceId := false
720+ for _ , f := range basicWrapper .SubFilter .FilterFields {
721+ if f .FieldName == loop_span .SpanFieldSpaceId {
722+ foundSpaceId = true
723+ assert .NotNil (t , f .QueryType )
724+ assert .Equal (t , loop_span .QueryTypeEnumExist , * f .QueryType )
725+ }
726+ }
727+ assert .True (t , foundSpaceId , "Modified SpaceId filter not found in preserved filters" )
728+
729+ // 2. Request filter
730+ reqWrapper := originalFilters .FilterFields [1 ]
731+ assert .Equal (t , requestFilter , reqWrapper .SubFilter )
732+
733+ return & repo.GetMetricsResult {
734+ Data : []map [string ]any {},
735+ }, nil
736+ },
737+ )
738+
739+ metricDef := & testMetricDefinition {
740+ name : "metric_a" ,
741+ metricType : entity .MetricTypeTimeSeries ,
742+ where : []* loop_span.FilterField {},
743+ }
744+ pMetrics := & entity.PlatformMetrics {
745+ MetricGroups : map [string ]* entity.MetricGroup {
746+ "test_group" : {
747+ MetricDefinitions : []entity.IMetricDefinition {metricDef },
748+ },
749+ },
750+ DrillDownObjects : map [string ]* loop_span.FilterField {},
751+ PlatformMetricDefs : map [loop_span.PlatformType ]* entity.PlatformMetricDef {
752+ loop_span .PlatformType ("loop" ): {
753+ MetricGroups : []string {"test_group" },
754+ },
755+ },
756+ }
757+
758+ svc , err := NewMetricsService (repoMock , nil , tenantMock , builderMock , traceConfigMock , pMetrics )
759+ assert .NoError (t , err )
760+
761+ _ , err = svc .QueryMetrics (context .Background (), & QueryMetricsReq {
762+ PlatformType : loop_span .PlatformType ("loop" ),
763+ WorkspaceID : 1 ,
764+ MetricsNames : []string {"metric_a" },
765+ Granularity : entity .MetricGranularity1Hour ,
766+ StartTime : 0 ,
767+ EndTime : 0 ,
768+ GroupBySpaceID : true ,
769+ FilterFields : requestFilter ,
770+ })
771+ assert .NoError (t , err )
772+ }
0 commit comments