diff --git a/cli/internal/controller/ad/ad.go b/cli/internal/controller/ad/ad.go index 9a3f4f05..6079d201 100644 --- a/cli/internal/controller/ad/ad.go +++ b/cli/internal/controller/ad/ad.go @@ -35,12 +35,14 @@ type Controller interface { StartDetector(context.Context, string) error StopDetector(context.Context, string) error DeleteDetector(context.Context, string, bool, bool) error + GetDetector(context.Context, string) (*entity.DetectorOutput, error) CreateAnomalyDetector(context.Context, entity.CreateDetectorRequest) (*string, error) CreateMultiEntityAnomalyDetector(ctx context.Context, request entity.CreateDetectorRequest, interactive bool, display bool) ([]string, error) SearchDetectorByName(context.Context, string) ([]entity.Detector, error) StartDetectorByName(context.Context, string, bool) error StopDetectorByName(context.Context, string, bool) error DeleteDetectorByName(context.Context, string, bool, bool) error + GetDetectorsByName(context.Context, string, bool) ([]*entity.DetectorOutput, error) } type controller struct { @@ -160,6 +162,24 @@ func (c controller) DeleteDetector(ctx context.Context, id string, interactive b } return nil } + +//GetDetector fetch detector based on DetectorID +func (c controller) GetDetector(ctx context.Context, ID string) (*entity.DetectorOutput, error) { + if len(ID) < 1 { + return nil, fmt.Errorf("detector Id: %s cannot be empty", ID) + } + response, err := c.gateway.GetDetector(ctx, ID) + if err != nil { + return nil, err + } + var data entity.DetectorResponse + err = json.Unmarshal(response, &data) + if err != nil { + return nil, err + } + return mapper.MapToDetectorOutput(data) +} + func processEntityError(err error) error { var c entity.CreateError data := fmt.Sprintf("%v", err) @@ -467,5 +487,35 @@ func (c controller) DeleteDetectorByName(ctx context.Context, name string, force } } return nil +} +//GetDetectorsByName get detector based on name pattern. It first calls SearchDetectorByName and then +// gets lists of detectorId and call GetDetector to get individual detector configuration +func (c controller) GetDetectorsByName(ctx context.Context, pattern string, display bool) ([]*entity.DetectorOutput, error) { + matchedDetectors, err := c.getDetectorsToProcess(ctx, "fetch", pattern) + if err != nil { + return nil, err + } + if matchedDetectors == nil { + return nil, nil + } + var bar *pb.ProgressBar + if display { + bar = createProgressBar(len(matchedDetectors)) + } + var output []*entity.DetectorOutput + for _, detector := range matchedDetectors { + data, err := c.GetDetector(ctx, detector.ID) + if err != nil { + return nil, err + } + output = append(output, data) + if bar != nil { + bar.Increment() + } + } + if bar != nil { + bar.Finish() + } + return output, nil } diff --git a/cli/internal/controller/ad/ad_test.go b/cli/internal/controller/ad/ad_test.go index 22590459..1e78a200 100644 --- a/cli/internal/controller/ad/ad_test.go +++ b/cli/internal/controller/ad/ad_test.go @@ -724,3 +724,89 @@ func TestController_DeleteDetectorByName(t *testing.T) { assert.NoError(t, err) }) } + +func TestController_GetDetectorByName(t *testing.T) { + detectorOutput := &entity.DetectorOutput{ + ID: "detectorID", + Name: "detector", + Description: "Test detector", + TimeField: "timestamp", + Index: []string{"order*"}, + Features: []entity.Feature{ + { + Name: "total_order", + Enabled: true, + AggregationQuery: []byte(`{"total_order":{"sum":{"field":"value"}}}`), + }, + }, + Filter: []byte(`{"bool" : {"filter" : [{"exists" : {"field" : "value","boost" : 1.0}}],"adjust_pure_negative" : true,"boost" : 1.0}}`), + Interval: "5m", + Delay: "1m", + LastUpdatedAt: 1589441737319, + SchemaVersion: 0, + } + t.Run("get empty detector", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockADGateway := adgateway.NewMockGateway(mockCtrl) + mockESController := esmockctrl.NewMockController(mockCtrl) + ctx := context.Background() + ctrl := New(os.Stdin, mockESController, mockADGateway) + _, err := ctrl.GetDetectorsByName(ctx, "", false) + assert.Error(t, err) + }) + t.Run("search detector gateway failed", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + ctx := context.Background() + mockADGateway := adgateway.NewMockGateway(mockCtrl) + mockADGateway.EXPECT().SearchDetector(ctx, getSearchPayload("detector")).Return(nil, errors.New("gateway failed")) + mockESController := esmockctrl.NewMockController(mockCtrl) + ctrl := New(os.Stdin, mockESController, mockADGateway) + _, err := ctrl.GetDetectorsByName(ctx, "detector", false) + assert.EqualError(t, err, "gateway failed") + }) + t.Run("search detector gateway returned empty", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + ctx := context.Background() + mockADGateway := adgateway.NewMockGateway(mockCtrl) + mockADGateway.EXPECT().SearchDetector(ctx, getSearchPayload("detector")).Return([]byte(`{}`), nil) + mockESController := esmockctrl.NewMockController(mockCtrl) + ctrl := New(os.Stdin, mockESController, mockADGateway) + actual, err := ctrl.GetDetectorsByName(ctx, "detector", false) + assert.NoError(t, err) + assert.Nil(t, actual) + }) + t.Run("get detector gateway failed", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + ctx := context.Background() + mockADGateway := adgateway.NewMockGateway(mockCtrl) + mockADGateway.EXPECT().SearchDetector(ctx, getSearchPayload("detector")).Return( + helperLoadBytes(t, "search_response.json"), nil) + mockADGateway.EXPECT().GetDetector(ctx, "detectorID").Return(nil, errors.New("gateway failed")) + var stdin bytes.Buffer + stdin.Write([]byte("yes\n")) + mockESController := esmockctrl.NewMockController(mockCtrl) + ctrl := New(&stdin, mockESController, mockADGateway) + _, err := ctrl.GetDetectorsByName(ctx, "detector", false) + assert.EqualError(t, err, "gateway failed") + }) + t.Run("get detector", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + ctx := context.Background() + mockADGateway := adgateway.NewMockGateway(mockCtrl) + mockADGateway.EXPECT().SearchDetector(ctx, getSearchPayload("detector")).Return( + helperLoadBytes(t, "search_response.json"), nil) + mockADGateway.EXPECT().GetDetector(ctx, "detectorID").Return(helperLoadBytes(t, "get_response.json"), nil) + mockESController := esmockctrl.NewMockController(mockCtrl) + var stdin bytes.Buffer + stdin.Write([]byte("yes\n")) + ctrl := New(&stdin, mockESController, mockADGateway) + res, err := ctrl.GetDetectorsByName(ctx, "detector", false) + assert.NoError(t, err) + assert.EqualValues(t, *res[0], *detectorOutput) + }) +} diff --git a/cli/internal/controller/ad/mocks/mock_ad.go b/cli/internal/controller/ad/mocks/mock_ad.go index c766e8ca..21afcfa9 100644 --- a/cli/internal/controller/ad/mocks/mock_ad.go +++ b/cli/internal/controller/ad/mocks/mock_ad.go @@ -92,6 +92,36 @@ func (mr *MockControllerMockRecorder) DeleteDetectorByName(arg0, arg1, arg2, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDetectorByName", reflect.TypeOf((*MockController)(nil).DeleteDetectorByName), arg0, arg1, arg2, arg3) } +// GetDetector mocks base method +func (m *MockController) GetDetector(arg0 context.Context, arg1 string) (*ad.DetectorOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDetector", arg0, arg1) + ret0, _ := ret[0].(*ad.DetectorOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDetector indicates an expected call of GetDetector +func (mr *MockControllerMockRecorder) GetDetector(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDetector", reflect.TypeOf((*MockController)(nil).GetDetector), arg0, arg1) +} + +// GetDetectorsByName mocks base method +func (m *MockController) GetDetectorsByName(arg0 context.Context, arg1 string, arg2 bool) ([]*ad.DetectorOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDetectorsByName", arg0, arg1, arg2) + ret0, _ := ret[0].([]*ad.DetectorOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDetectorsByName indicates an expected call of GetDetectorsByName +func (mr *MockControllerMockRecorder) GetDetectorsByName(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDetectorsByName", reflect.TypeOf((*MockController)(nil).GetDetectorsByName), arg0, arg1, arg2) +} + // SearchDetectorByName mocks base method func (m *MockController) SearchDetectorByName(arg0 context.Context, arg1 string) ([]ad.Detector, error) { m.ctrl.T.Helper() diff --git a/cli/internal/controller/ad/testdata/get_response.json b/cli/internal/controller/ad/testdata/get_response.json index e69de29b..dd0cfaa9 100644 --- a/cli/internal/controller/ad/testdata/get_response.json +++ b/cli/internal/controller/ad/testdata/get_response.json @@ -0,0 +1,37 @@ +{ + "_id" : "detectorID", + "_version" : 1, + "_primary_term" : 1, + "_seq_no" : 3, + "anomaly_detector" : { + "name" : "detector", + "description" : "Test detector", + "time_field" : "timestamp", + "indices" : [ + "order*" + ], + "filter_query" : {"bool" : {"filter" : [{"exists" : {"field" : "value","boost" : 1.0}}],"adjust_pure_negative" : true,"boost" : 1.0}}, + "detection_interval" : { + "period" : { + "interval" : 5, + "unit" : "Minutes" + } + }, + "window_delay" : { + "period" : { + "interval" : 1, + "unit" : "Minutes" + } + }, + "schema_version" : 0, + "feature_attributes" : [ + { + "feature_id" : "mYccEnIBTXsGi3mvMd8_", + "feature_name" : "total_order", + "feature_enabled" : true, + "aggregation_query" : {"total_order":{"sum":{"field":"value"}}} + } + ], + "last_update_time" : 1589441737319 + } +} \ No newline at end of file