This is an automated email from the ASF dual-hosted git repository.

likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 8d24b4059 refactor(framework): use generic to optimize (#4659)
8d24b4059 is described below

commit 8d24b40590cf01b199faf52a5f12408db80aa1eb
Author: Warren Chen <[email protected]>
AuthorDate: Tue Mar 14 18:20:04 2023 +0800

    refactor(framework): use generic to optimize (#4659)
---
 backend/helpers/pluginhelper/api/scope_helper.go   | 214 +++++++++------------
 .../helpers/pluginhelper/api/scope_helper_test.go  | 112 ++++++-----
 backend/plugins/github/api/init.go                 |   5 +-
 backend/plugins/github/api/scope.go                |  37 +---
 backend/plugins/github/models/repo.go              |   4 +-
 backend/plugins/gitlab/api/init.go                 |   5 +-
 backend/plugins/gitlab/api/scope.go                |  42 +---
 7 files changed, 184 insertions(+), 235 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/scope_helper.go 
b/backend/helpers/pluginhelper/api/scope_helper.go
index 0901f69d8..962002a69 100644
--- a/backend/helpers/pluginhelper/api/scope_helper.go
+++ b/backend/helpers/pluginhelper/api/scope_helper.go
@@ -35,7 +35,7 @@ import (
 )
 
 // ScopeApiHelper is used to write the CURD of connection
-type ScopeApiHelper struct {
+type ScopeApiHelper[Conn any, Scope any, Tr any] struct {
        log        log.Logger
        db         dal.Dal
        validator  *validator.Validate
@@ -43,18 +43,18 @@ type ScopeApiHelper struct {
 }
 
 // NewScopeHelper creates a ScopeHelper for connection management
-func NewScopeHelper(
+func NewScopeHelper[Conn any, Scope any, Tr any](
        basicRes context.BasicRes,
        vld *validator.Validate,
        connHelper *ConnectionApiHelper,
-) *ScopeApiHelper {
+) *ScopeApiHelper[Conn, Scope, Tr] {
        if vld == nil {
                vld = validator.New()
        }
        if connHelper == nil {
                return nil
        }
-       return &ScopeApiHelper{
+       return &ScopeApiHelper[Conn, Scope, Tr]{
                log:        basicRes.GetLogger(),
                db:         basicRes.GetDal(),
                validator:  vld,
@@ -62,178 +62,169 @@ func NewScopeHelper(
        }
 }
 
+type ScopeRes[T any] struct {
+       Scope                  T
+       TransformationRuleName string `json:"transformationRuleName,omitempty"`
+}
+
+type ScopeReq[T any] struct {
+       Data []*T `json:"data"`
+}
+
 // Put saves the given scopes to the database. It expects a slice of struct 
pointers
 // as the scopes argument. It also expects a fieldName argument, which is used 
to extract
 // the connection ID from the input.Params map.
-func (c *ScopeApiHelper) Put(input *plugin.ApiResourceInput, apiScope 
interface{}, connection interface{}) errors.Error {
-       err := errors.Convert(mapstructure.Decode(input.Body, apiScope))
-       if err != nil {
-               return errors.BadInput.Wrap(err, "decoding Github repo error")
+func (c *ScopeApiHelper[Conn, Scope, Tr]) Put(input *plugin.ApiResourceInput) 
([]*Scope, errors.Error) {
+       var req struct {
+               Data []*Scope `json:"data"`
        }
-       // Ensure that the scopes argument is a slice
-       v := reflect.ValueOf(apiScope)
-       scopesValue := v.Elem().FieldByName("Data")
-       if scopesValue.Kind() != reflect.Slice {
-               panic("expected a slice")
+       err := errors.Convert(mapstructure.Decode(input.Body, &req))
+       if err != nil {
+               return nil, errors.BadInput.Wrap(err, "decoding Github repo 
error")
        }
        // Extract the connection ID from the input.Params map
-       connectionId, _ := ExtractParam(input.Params)
+       connectionId, _ := extractFromReqParam(input.Params)
        if connectionId == 0 {
-               return errors.BadInput.New("invalid connectionId or scopeId")
+               return nil, errors.BadInput.New("invalid connectionId or 
scopeId")
        }
-       err = c.VerifyConnection(connection, connectionId)
+       err = c.VerifyConnection(connectionId)
        if err != nil {
-               return err
+               return nil, err
        }
        // Create a map to keep track of primary key values
        keeper := make(map[string]struct{})
 
        // Set the CreatedDate and UpdatedDate fields to the current time for 
each scope
        now := time.Now()
-       for i := 0; i < scopesValue.Len(); i++ {
-               // Get the reflect.Value of the i-th struct pointer in the slice
-               structValue := scopesValue.Index(i)
-
-               // Ensure that the structValue is a pointer to a struct
-               if structValue.Kind() != reflect.Ptr || 
structValue.Elem().Kind() != reflect.Struct {
-                       panic("expected a pointer to a struct")
-               }
-
+       for _, v := range req.Data {
                // Ensure that the primary key value is unique
-               primaryValueStr := 
ReturnPrimaryKeyValue(structValue.Elem().Interface())
+               primaryValueStr := returnPrimaryKeyValue(*v)
                if _, ok := keeper[primaryValueStr]; ok {
-                       return errors.BadInput.New("duplicated item")
+                       return nil, errors.BadInput.New("duplicated item")
                } else {
                        keeper[primaryValueStr] = struct{}{}
                }
 
                // Set the connection ID, CreatedDate, and UpdatedDate fields
-               SetScopeFields(structValue.Interface(), connectionId, &now, 
&now)
+               setScopeFields(v, connectionId, &now, &now)
 
                // Verify that the primary key value is valid
-               err = VerifyPrimaryKeyValue(structValue.Elem().Interface())
+               err = VerifyScope(v, c.validator)
                if err != nil {
-                       return err
+                       return nil, err
                }
        }
 
        // Save the scopes to the database
-       return c.save(scopesValue.Interface(), c.db.Create)
+       return req.Data, c.save(&req.Data)
 }
 
-func (c *ScopeApiHelper) Update(input *plugin.ApiResourceInput, fieldName 
string, connection interface{}, scope interface{}) errors.Error {
-       connectionId, scopeId := ExtractParam(input.Params)
+func (c *ScopeApiHelper[Conn, Scope, Tr]) Update(input 
*plugin.ApiResourceInput, fieldName string) (*Scope, errors.Error) {
+       connectionId, scopeId := extractFromReqParam(input.Params)
 
        if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {
-               return errors.BadInput.New("invalid connectionId")
+               return nil, errors.BadInput.New("invalid connectionId")
        }
-       err := c.VerifyConnection(connection, connectionId)
+       err := c.VerifyConnection(connectionId)
        if err != nil {
-               return err
+               return nil, err
        }
-
-       err = c.db.First(scope, dal.Where(fmt.Sprintf("connection_id = ? AND %s 
= ?", fieldName), connectionId, scopeId))
+       var scope Scope
+       err = c.db.First(&scope, dal.Where(fmt.Sprintf("connection_id = ? AND 
%s = ?", fieldName), connectionId, scopeId))
        if err != nil {
-               return errors.Default.New("getting Scope error")
+               return nil, errors.Default.New("getting Scope error")
        }
-       err = DecodeMapStruct(input.Body, scope)
+       err = DecodeMapStruct(input.Body, &scope)
        if err != nil {
-               return errors.Default.Wrap(err, "patch scope error")
+               return nil, errors.Default.Wrap(err, "patch scope error")
        }
-       err = VerifyPrimaryKeyValue(scope)
+       err = VerifyScope(&scope, c.validator)
        if err != nil {
-               return err
+               return nil, errors.Default.Wrap(err, "Invalid scope")
        }
+
        err = c.db.Update(scope)
        if err != nil {
-               return errors.Default.Wrap(err, "error on saving Scope")
+               return nil, errors.Default.Wrap(err, "error on saving Scope")
        }
-       return nil
+       return &scope, nil
 }
 
-func (c *ScopeApiHelper) GetScopeList(input *plugin.ApiResourceInput, 
connection interface{}, scopes interface{}, rules interface{}) 
(map[uint64]string, errors.Error) {
-       connectionId, _ := ExtractParam(input.Params)
+func (c *ScopeApiHelper[Conn, Scope, Tr]) GetScopeList(input 
*plugin.ApiResourceInput) ([]ScopeRes[Scope], errors.Error) {
+       connectionId, _ := extractFromReqParam(input.Params)
        if connectionId == 0 {
                return nil, errors.BadInput.New("invalid path params")
        }
-       err := c.VerifyConnection(connection, connectionId)
+       err := c.VerifyConnection(connectionId)
        if err != nil {
                return nil, err
        }
        limit, offset := GetLimitOffset(input.Query, "pageSize", "page")
-       err = c.db.All(scopes, dal.Where("connection_id = ?", connectionId), 
dal.Limit(limit), dal.Offset(offset))
+       var scopes []*Scope
+       err = c.db.All(&scopes, dal.Where("connection_id = ?", connectionId), 
dal.Limit(limit), dal.Offset(offset))
        if err != nil {
                return nil, err
        }
 
-       scopesValue := 
reflect.ValueOf(reflect.ValueOf(scopes).Elem().Interface())
-       if scopesValue.Kind() != reflect.Slice {
-               panic("expected a slice")
-       }
        var ruleIds []uint64
-       for i := 0; i < scopesValue.Len(); i++ {
-               // Get the reflect.Value of the i-th struct pointer in the slice
-               structValue := scopesValue.Index(i)
-
-               // Ensure that the structValue is a pointer to a struct
-               if structValue.Kind() != reflect.Ptr || 
structValue.Elem().Kind() != reflect.Struct {
-                       panic("expected a pointer to a struct")
-               }
-               ruleId := 
structValue.Elem().FieldByName("TransformationRuleId").Uint()
+       for _, structValue := range scopes {
+               ruleId := 
reflect.ValueOf(structValue).Elem().FieldByName("TransformationRuleId").Uint()
                if ruleId > 0 {
                        ruleIds = append(ruleIds, ruleId)
                }
        }
-
+       var rules []*Tr
        if len(ruleIds) > 0 {
-               err = c.db.All(rules, dal.Where("id IN (?)", ruleIds))
+               err = c.db.All(&rules, dal.Where("id IN (?)", ruleIds))
                if err != nil {
                        return nil, err
                }
        }
-       rulesValue := reflect.ValueOf(reflect.ValueOf(rules).Elem().Interface())
-       if scopesValue.Kind() != reflect.Slice {
-               panic("expected a slice")
-       }
        names := make(map[uint64]string)
-       for i := 0; i < rulesValue.Len(); i++ {
+       for _, rule := range rules {
                // Get the reflect.Value of the i-th struct pointer in the slice
-               structValue := rulesValue.Index(i)
-               names[structValue.FieldByName("ID").Uint()] = 
structValue.FieldByName("Name").String()
+               names[reflect.ValueOf(rule).Elem().FieldByName("ID").Uint()] = 
reflect.ValueOf(rule).Elem().FieldByName("Name").String()
        }
-       return names, nil
+       apiScopes := make([]ScopeRes[Scope], 0)
+       for _, scope := range scopes {
+               apiScopes = append(apiScopes, ScopeRes[Scope]{Scope: *scope, 
TransformationRuleName: 
names[reflect.ValueOf(scope).Elem().FieldByName("TransformationRuleId").Uint()]})
+       }
+       return apiScopes, nil
 }
 
-func (c *ScopeApiHelper) GetScope(input *plugin.ApiResourceInput, fieldName 
string, connection interface{}, scope interface{}, rule interface{}) 
errors.Error {
-       connectionId, scopeId := ExtractParam(input.Params)
+func (c *ScopeApiHelper[Conn, Scope, Tr]) GetScope(input 
*plugin.ApiResourceInput, fieldName string) (*ScopeRes[Scope], errors.Error) {
+       connectionId, scopeId := extractFromReqParam(input.Params)
        if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {
-               return errors.BadInput.New("invalid path params")
+               return nil, errors.BadInput.New("invalid path params")
        }
-       err := c.VerifyConnection(connection, connectionId)
+       err := c.VerifyConnection(connectionId)
        if err != nil {
-               return err
+               return nil, err
        }
        db := c.db
        query := dal.Where(fmt.Sprintf("connection_id = ? AND %s = ?", 
fieldName), connectionId, scopeId)
-       err = db.First(scope, query)
+       var scope Scope
+       err = db.First(&scope, query)
        if db.IsErrorNotFound(err) {
-               return errors.NotFound.New("Scope not found")
+               return nil, errors.NotFound.New("Scope not found")
        }
        if err != nil {
-               return err
+               return nil, err
        }
-       repoRuleId := 
reflect.ValueOf(scope).Elem().FieldByName("TransformationRuleId").Uint()
+       repoRuleId := 
reflect.ValueOf(scope).FieldByName("TransformationRuleId").Uint()
+       var rule Tr
        if repoRuleId > 0 {
-               err = db.First(rule, dal.Where("id = ?", repoRuleId))
+               err = db.First(&rule, dal.Where("id = ?", repoRuleId))
                if err != nil {
-                       return errors.NotFound.New("transformationRule not 
found")
+                       return nil, errors.NotFound.New("transformationRule not 
found")
                }
        }
-       return nil
+       return &ScopeRes[Scope]{Scope: scope, TransformationRuleName: 
reflect.ValueOf(rule).FieldByName("Name").String()}, nil
 }
 
-func (c *ScopeApiHelper) VerifyConnection(connection interface{}, connId 
uint64) errors.Error {
-       err := c.connHelper.FirstById(&connection, connId)
+func (c *ScopeApiHelper[Conn, Scope, Tr]) VerifyConnection(connId uint64) 
errors.Error {
+       var conn Conn
+       err := c.connHelper.FirstById(&conn, connId)
        if err != nil {
                if errors.Is(err, gorm.ErrRecordNotFound) {
                        return errors.BadInput.New("Invalid Connection Id")
@@ -243,7 +234,7 @@ func (c *ScopeApiHelper) VerifyConnection(connection 
interface{}, connId uint64)
        return nil
 }
 
-func (c *ScopeApiHelper) save(scope interface{}, method func(entity 
interface{}, clauses ...dal.Clause) errors.Error) errors.Error {
+func (c *ScopeApiHelper[Conn, Scope, Tr]) save(scope interface{}) errors.Error 
{
        err := c.db.CreateOrUpdate(scope)
        if err != nil {
                if c.db.IsDuplicationError(err) {
@@ -254,7 +245,7 @@ func (c *ScopeApiHelper) save(scope interface{}, method 
func(entity interface{},
        return nil
 }
 
-func ExtractParam(params map[string]string) (uint64, string) {
+func extractFromReqParam(params map[string]string) (uint64, string) {
        connectionId, err := strconv.ParseUint(params["connectionId"], 10, 64)
        if err != nil {
                return 0, ""
@@ -263,33 +254,7 @@ func ExtractParam(params map[string]string) (uint64, 
string) {
        return connectionId, scopeId
 }
 
-// VerifyPrimaryKeyValue function verifies that the primary key value of a 
given struct instance is not zero or empty.
-func VerifyPrimaryKeyValue(i interface{}) errors.Error {
-       var value reflect.Value
-       pType := reflect.TypeOf(i)
-       if pType.Kind() == reflect.Ptr {
-               value = reflect.ValueOf(reflect.ValueOf(i).Elem().Interface())
-       } else {
-               value = reflect.ValueOf(i)
-       }
-       // Loop through the fields of the input struct using reflection
-       for j := 0; j < value.NumField(); j++ {
-               field := value.Field(j)
-               tag := value.Type().Field(j).Tag.Get("gorm")
-
-               // Check if the field is tagged as a primary key using the GORM 
tag "primaryKey"
-               if strings.Contains(tag, "primaryKey") {
-                       // If the field value is zero or nil, return an error 
indicating that the primary key value is invalid
-                       if field.Interface() == 
reflect.Zero(field.Type()).Interface() || field.Interface() == nil {
-                               return errors.Default.New("primary key value is 
zero or empty")
-                       }
-               }
-       }
-       // If all primary key values are valid, return nil (no error)
-       return nil
-}
-
-func SetScopeFields(p interface{}, connectionId uint64, createdDate 
*time.Time, updatedDate *time.Time) {
+func setScopeFields(p interface{}, connectionId uint64, createdDate 
*time.Time, updatedDate *time.Time) {
        pType := reflect.TypeOf(p)
        if pType.Kind() != reflect.Ptr {
                panic("expected a pointer to a struct")
@@ -316,10 +281,10 @@ func SetScopeFields(p interface{}, connectionId uint64, 
createdDate *time.Time,
        }
 }
 
-// ReturnPrimaryKeyValue returns a string containing the primary key value(s) 
of a struct, concatenated with "-" between them.
+// returnPrimaryKeyValue returns a string containing the primary key value(s) 
of a struct, concatenated with "-" between them.
 // This function receives an interface{} type argument p, which can be a 
pointer to any struct.
 // The function uses reflection to iterate through the fields of the struct, 
and checks if each field is tagged as "primaryKey".
-func ReturnPrimaryKeyValue(p interface{}) string {
+func returnPrimaryKeyValue(p interface{}) string {
        result := ""
        // get the type and value of the input interface using reflection
        t := reflect.TypeOf(p)
@@ -344,3 +309,16 @@ func ReturnPrimaryKeyValue(p interface{}) string {
        // return the final primary key value as a string
        return result
 }
+
+func VerifyScope(scope interface{}, vld *validator.Validate) errors.Error {
+       if vld != nil {
+               pType := reflect.TypeOf(scope)
+               if pType.Kind() != reflect.Ptr {
+                       panic("expected a pointer to a struct")
+               }
+               if err := vld.Struct(scope); err != nil {
+                       return errors.Default.Wrap(err, "error validating 
target")
+               }
+       }
+       return nil
+}
diff --git a/backend/helpers/pluginhelper/api/scope_helper_test.go 
b/backend/helpers/pluginhelper/api/scope_helper_test.go
index ad29684d9..a0e0e9afe 100644
--- a/backend/helpers/pluginhelper/api/scope_helper_test.go
+++ b/backend/helpers/pluginhelper/api/scope_helper_test.go
@@ -23,8 +23,10 @@ import (
        "github.com/apache/incubator-devlake/helpers/unithelper"
        mockcontext "github.com/apache/incubator-devlake/mocks/core/context"
        mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
+       "github.com/go-playground/validator/v10"
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/mock"
+       "gorm.io/datatypes"
        "gorm.io/gorm"
        "reflect"
        "testing"
@@ -32,7 +34,7 @@ import (
 )
 
 type TestModel struct {
-       ID   uint   `gorm:"primaryKey"`
+       ID   uint   `gorm:"primaryKey" validate:"required"`
        Name string `gorm:"primaryKey;type:BIGINT  NOT NULL"`
 }
 
@@ -70,11 +72,7 @@ func (GithubConnection) TableName() string {
        return "_tool_github_connections"
 }
 
-type req struct {
-       Data []*GithubRepo `json:"data"`
-}
-
-func TestCheckPrimaryKeyValue(t *testing.T) {
+func TestVerifyScope(t *testing.T) {
        testCases := []struct {
                name    string
                model   TestModel
@@ -102,12 +100,12 @@ func TestCheckPrimaryKeyValue(t *testing.T) {
                                ID:   1,
                                Name: "",
                        },
-                       wantErr: true,
+                       wantErr: false,
                },
        }
 
        for _, tc := range testCases {
-               err := VerifyPrimaryKeyValue(tc.model)
+               err := VerifyScope(&tc.model, validator.New())
                if (err != nil) != tc.wantErr {
                        t.Errorf("unexpected error value - got: %v, want: %v", 
err, tc.wantErr)
                }
@@ -115,6 +113,27 @@ func TestCheckPrimaryKeyValue(t *testing.T) {
        }
 }
 
+type GithubTransformationRule struct {
+       common.Model         `mapstructure:"-"`
+       Name                 string            `mapstructure:"name" json:"name" 
gorm:"type:varchar(255);index:idx_name_github,unique" validate:"required"`
+       PrType               string            `mapstructure:"prType,omitempty" 
json:"prType" gorm:"type:varchar(255)"`
+       PrComponent          string            
`mapstructure:"prComponent,omitempty" json:"prComponent" 
gorm:"type:varchar(255)"`
+       PrBodyClosePattern   string            
`mapstructure:"prBodyClosePattern,omitempty" json:"prBodyClosePattern" 
gorm:"type:varchar(255)"`
+       IssueSeverity        string            
`mapstructure:"issueSeverity,omitempty" json:"issueSeverity" 
gorm:"type:varchar(255)"`
+       IssuePriority        string            
`mapstructure:"issuePriority,omitempty" json:"issuePriority" 
gorm:"type:varchar(255)"`
+       IssueComponent       string            
`mapstructure:"issueComponent,omitempty" json:"issueComponent" 
gorm:"type:varchar(255)"`
+       IssueTypeBug         string            
`mapstructure:"issueTypeBug,omitempty" json:"issueTypeBug" 
gorm:"type:varchar(255)"`
+       IssueTypeIncident    string            
`mapstructure:"issueTypeIncident,omitempty" json:"issueTypeIncident" 
gorm:"type:varchar(255)"`
+       IssueTypeRequirement string            
`mapstructure:"issueTypeRequirement,omitempty" json:"issueTypeRequirement" 
gorm:"type:varchar(255)"`
+       DeploymentPattern    string            
`mapstructure:"deploymentPattern,omitempty" json:"deploymentPattern" 
gorm:"type:varchar(255)"`
+       ProductionPattern    string            
`mapstructure:"productionPattern,omitempty" json:"productionPattern" 
gorm:"type:varchar(255)"`
+       Refdiff              datatypes.JSONMap 
`mapstructure:"refdiff,omitempty" json:"refdiff" swaggertype:"object" 
format:"json"`
+}
+
+func (GithubTransformationRule) TableName() string {
+       return "_tool_github_transformation_rules"
+}
+
 func TestSetGitlabProjectFields(t *testing.T) {
        // create a struct
        var p struct {
@@ -126,11 +145,11 @@ func TestSetGitlabProjectFields(t *testing.T) {
                common.NoPKModel `json:"-" mapstructure:"-"`
        }
 
-       // call SetScopeFields to assign value
+       // call setScopeFields to assign value
        connectionId := uint64(123)
        createdDate := time.Now()
        updatedDate := &createdDate
-       SetScopeFields(&p, connectionId, &createdDate, updatedDate)
+       setScopeFields(&p, connectionId, &createdDate, updatedDate)
 
        // verify fields
        if p.ConnectionId != connectionId {
@@ -147,7 +166,7 @@ func TestSetGitlabProjectFields(t *testing.T) {
                t.Errorf("UpdatedDate not set correctly, expected: %v, got: 
%v", updatedDate, p.UpdatedDate)
        }
 
-       SetScopeFields(&p, connectionId, &createdDate, nil)
+       setScopeFields(&p, connectionId, &createdDate, nil)
 
        // verify fields
        if p.ConnectionId != connectionId {
@@ -183,10 +202,10 @@ func TestReturnPrimaryKeyValue(t *testing.T) {
        }
 
        // Call the function and check if it returns the correct primary key 
value.
-       result := ReturnPrimaryKeyValue(test)
+       result := returnPrimaryKeyValue(test)
        expected := "1-123"
        if result != expected {
-               t.Errorf("ReturnPrimaryKeyValue returned %s, expected %s", 
result, expected)
+               t.Errorf("returnPrimaryKeyValue returned %s, expected %s", 
result, expected)
        }
 
        // Test with a different struct that has no field with primaryKey tag.
@@ -203,10 +222,10 @@ func TestReturnPrimaryKeyValue(t *testing.T) {
                CreatedAt: time.Now(),
        }
 
-       result2 := ReturnPrimaryKeyValue(test2)
+       result2 := returnPrimaryKeyValue(test2)
        expected2 := ""
        if result2 != expected2 {
-               t.Errorf("ReturnPrimaryKeyValue returned %s, expected %s", 
result2, expected2)
+               t.Errorf("returnPrimaryKeyValue returned %s, expected %s", 
result2, expected2)
        }
 }
 
@@ -233,37 +252,42 @@ func TestScopeApiHelper_Put(t *testing.T) {
        connHelper := NewConnectionHelper(mockRes, nil)
 
        // create a mock input, scopes, and connection
-       input := &plugin.ApiResourceInput{Params: 
map[string]string{"connectionId": "123"}}
-       scopes := []*GithubRepo{
-               {GithubId: 1, Name: "scope1"},
-               {GithubId: 2, Name: "scope2"},
-       }
-       connection := &GithubConnection{}
-       connection.ID = 3
-       connection.Name = "test"
+       input := &plugin.ApiResourceInput{Params: 
map[string]string{"connectionId": "123"}, Body: map[string]interface{}{
+               "data": []map[string]interface{}{
+                       {
+                               "HTMLUrl":              "string",
+                               "githubId":             1,
+                               "cloneUrl":             "string",
+                               "connectionId":         1,
+                               "createdAt":            "string",
+                               "createdDate":          "string",
+                               "description":          "string",
+                               "language":             "string",
+                               "name":                 "string",
+                               "owner":                "string",
+                               "transformationRuleId": 0,
+                               "updatedAt":            "string",
+                               "updatedDate":          "string",
+                       },
+                       {
+                               "HTMLUrl":              "11",
+                               "githubId":             2,
+                               "cloneUrl":             "string",
+                               "connectionId":         1,
+                               "createdAt":            "string",
+                               "createdDate":          "string",
+                               "description":          "string",
+                               "language":             "string",
+                               "name":                 "string",
+                               "owner":                "string",
+                               "transformationRuleId": 0,
+                               "updatedAt":            "string",
+                               "updatedDate":          "string",
+                       }}}}
 
        // create a mock ScopeApiHelper with a mock database connection
-       apiHelper := &ScopeApiHelper{db: mockDal, connHelper: connHelper}
-       apiReq := req{Data: scopes}
+       apiHelper := &ScopeApiHelper[GithubConnection, GithubRepo, 
GithubTransformationRule]{db: mockDal, connHelper: connHelper}
        // test a successful call to Put
-       err := apiHelper.Put(input, &apiReq, connection)
+       _, err := apiHelper.Put(input)
        assert.NoError(t, err)
-
-       // test a call to Put with a duplicate primary key value
-       duplicateScopes := []*GithubRepo{
-               {GithubId: 1, Name: "scope1"},
-               {GithubId: 1, Name: "scope2"},
-       }
-       apiReq.Data = duplicateScopes
-       err = apiHelper.Put(input, &apiReq, connection)
-       assert.Error(t, err)
-
-       // test a call to Put with an invalid primary key value
-       invalidScopes := []*GithubRepo{
-               {GithubId: 0, Name: "scope1"},
-       }
-       apiReq.Data = invalidScopes
-
-       err = apiHelper.Put(input, &apiReq, connection)
-       assert.Error(t, err)
 }
diff --git a/backend/plugins/github/api/init.go 
b/backend/plugins/github/api/init.go
index 8f0dc3bad..3044a56ba 100644
--- a/backend/plugins/github/api/init.go
+++ b/backend/plugins/github/api/init.go
@@ -20,12 +20,13 @@ package api
 import (
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/github/models"
        "github.com/go-playground/validator/v10"
 )
 
 var vld *validator.Validate
 var connectionHelper *api.ConnectionApiHelper
-var scopeHelper *api.ScopeApiHelper
+var scopeHelper *api.ScopeApiHelper[models.GithubConnection, 
models.GithubRepo, models.GithubTransformationRule]
 var basicRes context.BasicRes
 
 func Init(br context.BasicRes) {
@@ -35,7 +36,7 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
-       scopeHelper = api.NewScopeHelper(
+       scopeHelper = api.NewScopeHelper[models.GithubConnection, 
models.GithubRepo, models.GithubTransformationRule](
                basicRes,
                vld,
                connectionHelper,
diff --git a/backend/plugins/github/api/scope.go 
b/backend/plugins/github/api/scope.go
index 245d6eb39..e4b2d4bfe 100644
--- a/backend/plugins/github/api/scope.go
+++ b/backend/plugins/github/api/scope.go
@@ -20,19 +20,9 @@ package api
 import (
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
-       "github.com/apache/incubator-devlake/plugins/github/models"
        "net/http"
 )
 
-type apiRepo struct {
-       models.GithubRepo
-       TransformationRuleName string `json:"transformationRuleName,omitempty"`
-}
-
-type req struct {
-       Data []*models.GithubRepo `json:"data"`
-}
-
 // PutScope create or update github repo
 // @Summary create or update github repo
 // @Description Create or update github repo
@@ -45,12 +35,11 @@ type req struct {
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/github/connections/{connectionId}/scopes [PUT]
 func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var repos req
-       err := scopeHelper.Put(input, &repos, &models.GithubConnection{})
+       repos, err := scopeHelper.Put(input)
        if err != nil {
                return nil, errors.Default.Wrap(err, "error on saving 
GithubRepo")
        }
-       return &plugin.ApiResourceOutput{Body: repos.Data, Status: 
http.StatusOK}, nil
+       return &plugin.ApiResourceOutput{Body: repos, Status: http.StatusOK}, 
nil
 }
 
 // UpdateScope patch to github repo
@@ -66,9 +55,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/github/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var repo models.GithubRepo
-       var conn models.GithubConnection
-       err := scopeHelper.Update(input, "github_id", &conn, &repo)
+       repo, err := scopeHelper.Update(input, "github_id")
        if err != nil {
                return &plugin.ApiResourceOutput{Body: nil, Status: 
http.StatusInternalServerError}, err
        }
@@ -87,18 +74,11 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/github/connections/{connectionId}/scopes/ [GET]
 func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var repos []*models.GithubRepo
-       var rules []*models.GithubTransformationRule
-       var conn models.GithubConnection
-       names, err := scopeHelper.GetScopeList(input, conn, &repos, &rules)
+       apiScopes, err := scopeHelper.GetScopeList(input)
        if err != nil {
                return nil, err
        }
-       var apiRepos []apiRepo
-       for _, repo := range repos {
-               apiRepos = append(apiRepos, apiRepo{*repo, 
names[repo.TransformationRuleId]})
-       }
-       return &plugin.ApiResourceOutput{Body: apiRepos, Status: 
http.StatusOK}, nil
+       return &plugin.ApiResourceOutput{Body: apiScopes, Status: 
http.StatusOK}, nil
 }
 
 // GetScope get one Github repo
@@ -112,12 +92,9 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/github/connections/{connectionId}/scopes/{id} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var repo models.GithubRepo
-       var rule models.GithubTransformationRule
-       var conn models.GithubConnection
-       err := scopeHelper.GetScope(input, "github_id", conn, &repo, &rule)
+       apiScope, err := scopeHelper.GetScope(input, "github_id")
        if err != nil {
                return nil, err
        }
-       return &plugin.ApiResourceOutput{Body: apiRepo{repo, rule.Name}, 
Status: http.StatusOK}, nil
+       return &plugin.ApiResourceOutput{Body: apiScope, Status: 
http.StatusOK}, nil
 }
diff --git a/backend/plugins/github/models/repo.go 
b/backend/plugins/github/models/repo.go
index de215b15a..9eda5e118 100644
--- a/backend/plugins/github/models/repo.go
+++ b/backend/plugins/github/models/repo.go
@@ -23,8 +23,8 @@ import (
 )
 
 type GithubRepo struct {
-       ConnectionId         uint64     `json:"connectionId" gorm:"primaryKey" 
mapstructure:"connectionId,omitempty"`
-       GithubId             int        `json:"githubId" gorm:"primaryKey" 
mapstructure:"githubId"`
+       ConnectionId         uint64     `json:"connectionId" gorm:"primaryKey" 
validate:"required" mapstructure:"connectionId,omitempty"`
+       GithubId             int        `json:"githubId" gorm:"primaryKey" 
validate:"required" mapstructure:"githubId"`
        Name                 string     `json:"name" gorm:"type:varchar(255)" 
mapstructure:"name,omitempty"`
        HTMLUrl              string     `json:"HTMLUrl" 
gorm:"type:varchar(255)" mapstructure:"HTMLUrl,omitempty"`
        Description          string     `json:"description" 
mapstructure:"description,omitempty"`
diff --git a/backend/plugins/gitlab/api/init.go 
b/backend/plugins/gitlab/api/init.go
index 8f0dc3bad..b4190c90b 100644
--- a/backend/plugins/gitlab/api/init.go
+++ b/backend/plugins/gitlab/api/init.go
@@ -20,12 +20,13 @@ package api
 import (
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/gitlab/models"
        "github.com/go-playground/validator/v10"
 )
 
 var vld *validator.Validate
 var connectionHelper *api.ConnectionApiHelper
-var scopeHelper *api.ScopeApiHelper
+var scopeHelper *api.ScopeApiHelper[models.GitlabConnection, 
models.GitlabProject, models.GitlabTransformationRule]
 var basicRes context.BasicRes
 
 func Init(br context.BasicRes) {
@@ -35,7 +36,7 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
-       scopeHelper = api.NewScopeHelper(
+       scopeHelper = api.NewScopeHelper[models.GitlabConnection, 
models.GitlabProject, models.GitlabTransformationRule](
                basicRes,
                vld,
                connectionHelper,
diff --git a/backend/plugins/gitlab/api/scope.go 
b/backend/plugins/gitlab/api/scope.go
index 4e660b9a2..e41c729ab 100644
--- a/backend/plugins/gitlab/api/scope.go
+++ b/backend/plugins/gitlab/api/scope.go
@@ -32,10 +32,6 @@ type apiProject struct {
        TransformationRuleName string `json:"transformationRuleName,omitempty"`
 }
 
-type req struct {
-       Data []*models.GitlabProject `json:"data"`
-}
-
 // PutScope create or update gitlab project
 // @Summary create or update gitlab project
 // @Description Create or update gitlab project
@@ -48,12 +44,11 @@ type req struct {
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/gitlab/connections/{connectionId}/scopes [PUT]
 func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var scopes req
-       err := scopeHelper.Put(input, &scopes, &models.GitlabConnection{})
+       repos, err := scopeHelper.Put(input)
        if err != nil {
                return nil, errors.Default.Wrap(err, "error on saving 
GithubRepo")
        }
-       return &plugin.ApiResourceOutput{Body: scopes.Data, Status: 
http.StatusOK}, nil
+       return &plugin.ApiResourceOutput{Body: repos, Status: http.StatusOK}, 
nil
 }
 
 // UpdateScope patch to gitlab project
@@ -69,28 +64,11 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/gitlab/connections/{connectionId}/scopes/{projectId} 
[PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connectionId, projectId := extractParam(input.Params)
-       if connectionId*projectId == 0 {
-               return nil, errors.BadInput.New("invalid connectionId or 
projectId")
-       }
-       var project models.GitlabProject
-       err := basicRes.GetDal().First(&project, dal.Where("connection_id = ? 
AND gitlab_id = ?", connectionId, projectId))
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "getting GitlabProject 
error")
-       }
-       err = api.DecodeMapStruct(input.Body, &project)
+       repo, err := scopeHelper.Update(input, "gitlab_id")
        if err != nil {
-               return nil, errors.Default.Wrap(err, "patch gitlab project 
error")
+               return &plugin.ApiResourceOutput{Body: nil, Status: 
http.StatusInternalServerError}, err
        }
-       err = verifyProject(&project)
-       if err != nil {
-               return nil, err
-       }
-       err = basicRes.GetDal().Update(project)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
GitlabProject")
-       }
-       return &plugin.ApiResourceOutput{Body: project, Status: http.StatusOK}, 
nil
+       return &plugin.ApiResourceOutput{Body: repo, Status: http.StatusOK}, nil
 }
 
 // GetScopeList get Gitlab projects
@@ -178,13 +156,3 @@ func extractParam(params map[string]string) (uint64, 
uint64) {
        projectId, _ := strconv.ParseUint(params["projectId"], 10, 64)
        return connectionId, projectId
 }
-
-func verifyProject(project *models.GitlabProject) errors.Error {
-       if project.ConnectionId == 0 {
-               return errors.BadInput.New("invalid connectionId")
-       }
-       if project.GitlabId <= 0 {
-               return errors.BadInput.New("invalid projectId")
-       }
-       return nil
-}

Reply via email to