You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by "warren830 (via GitHub)" <gi...@apache.org> on 2023/03/09 17:47:52 UTC

[GitHub] [incubator-devlake] warren830 opened a new pull request, #4632: refactor(framework): add scopeHelper

warren830 opened a new pull request, #4632:
URL: https://github.com/apache/incubator-devlake/pull/4632

   ### ⚠️ Pre Checklist
   
   > Please complete _ALL_ items in this checklist, and remove before submitting
   
   - [ ] I have read through the [Contributing Documentation](https://devlake.apache.org/community/).
   - [ ] I have added relevant tests.
   - [ ] I have added relevant documentation.
   - [ ] I will add labels to the PR, such as `pr-type/bug-fix`, `pr-type/feature-development`, etc.
   
   <!--
   Thanks for submitting a pull request!
   
   We appreciate you spending the time to work on these changes.
   Please fill out as many sections below as possible.
   -->
   
   ### Summary
   What does this PR do?
   
   ### Does this close any open issues?
   Closes xx
   
   ### Screenshots
   Include any relevant screenshots here.
   
   ### Other Information
   Any other information that is important to this PR.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-devlake] warren830 merged pull request #4632: refactor(framework): add scopeHelper

Posted by "warren830 (via GitHub)" <gi...@apache.org>.
warren830 merged PR #4632:
URL: https://github.com/apache/incubator-devlake/pull/4632


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-devlake] mindlesscloud commented on a diff in pull request #4632: refactor(framework): add scopeHelper

Posted by "mindlesscloud (via GitHub)" <gi...@apache.org>.
mindlesscloud commented on code in PR #4632:
URL: https://github.com/apache/incubator-devlake/pull/4632#discussion_r1135030790


##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil
+	}
+	return &ScopeApiHelper{
+		log:        basicRes.GetLogger(),
+		db:         basicRes.GetDal(),
+		validator:  vld,
+		connHelper: connHelper,
+	}
+}
+
+// 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")
+	}
+	// 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")
+	}
+	// Extract the connection ID from the input.Params map
+	connectionId, _ := ExtractParam(input.Params)
+	if connectionId == 0 {
+		return errors.BadInput.New("invalid connectionId or scopeId")
+	}
+	err = c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return 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")
+		}
+
+		// Ensure that the primary key value is unique
+		primaryValueStr := ReturnPrimaryKeyValue(structValue.Elem().Interface())
+		if _, ok := keeper[primaryValueStr]; ok {
+			return errors.BadInput.New("duplicated item")
+		} else {
+			keeper[primaryValueStr] = struct{}{}
+		}
+
+		// Set the connection ID, CreatedDate, and UpdatedDate fields
+		SetScopeFields(structValue.Interface(), connectionId, &now, &now)
+
+		// Verify that the primary key value is valid
+		err = VerifyPrimaryKeyValue(structValue.Elem().Interface())
+		if err != nil {
+			return err
+		}
+	}
+
+	// Save the scopes to the database
+	return c.save(scopesValue.Interface(), c.db.Create)
+}
+
+func (c *ScopeApiHelper) Update(input *plugin.ApiResourceInput, fieldName string, connection interface{}, scope interface{}) errors.Error {
+	connectionId, scopeId := ExtractParam(input.Params)
+
+	if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {

Review Comment:
   Shall we check for `scopeId == ""`?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-devlake] klesh commented on a diff in pull request #4632: refactor(framework): add scopeHelper

Posted by "klesh (via GitHub)" <gi...@apache.org>.
klesh commented on code in PR #4632:
URL: https://github.com/apache/incubator-devlake/pull/4632#discussion_r1135043016


##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil

Review Comment:
   I think we should either panic here or return an error.



##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil
+	}
+	return &ScopeApiHelper{
+		log:        basicRes.GetLogger(),
+		db:         basicRes.GetDal(),
+		validator:  vld,
+		connHelper: connHelper,
+	}
+}
+
+// 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

Review Comment:
   I don't see the `fieldName` argument appears in the function



##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil
+	}
+	return &ScopeApiHelper{
+		log:        basicRes.GetLogger(),
+		db:         basicRes.GetDal(),
+		validator:  vld,
+		connHelper: connHelper,
+	}
+}
+
+// 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 {

Review Comment:
   Seems like we need only the type information, can we use Generic instead of Reflection?
   Moreover, `scope` and `connection` are required for all Public Methods, with Generic they can be omitted.



##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil
+	}
+	return &ScopeApiHelper{
+		log:        basicRes.GetLogger(),
+		db:         basicRes.GetDal(),
+		validator:  vld,
+		connHelper: connHelper,
+	}
+}
+
+// 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")
+	}
+	// 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")
+	}
+	// Extract the connection ID from the input.Params map
+	connectionId, _ := ExtractParam(input.Params)
+	if connectionId == 0 {
+		return errors.BadInput.New("invalid connectionId or scopeId")
+	}
+	err = c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return 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")
+		}
+
+		// Ensure that the primary key value is unique
+		primaryValueStr := ReturnPrimaryKeyValue(structValue.Elem().Interface())
+		if _, ok := keeper[primaryValueStr]; ok {
+			return errors.BadInput.New("duplicated item")
+		} else {
+			keeper[primaryValueStr] = struct{}{}
+		}
+
+		// Set the connection ID, CreatedDate, and UpdatedDate fields
+		SetScopeFields(structValue.Interface(), connectionId, &now, &now)

Review Comment:
   Seems like this is the only place to call this function, maybe it is better to put the code directly here so the type-checking can be skipped.



##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil
+	}
+	return &ScopeApiHelper{
+		log:        basicRes.GetLogger(),
+		db:         basicRes.GetDal(),
+		validator:  vld,
+		connHelper: connHelper,
+	}
+}
+
+// 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")
+	}
+	// 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")
+	}
+	// Extract the connection ID from the input.Params map
+	connectionId, _ := ExtractParam(input.Params)
+	if connectionId == 0 {
+		return errors.BadInput.New("invalid connectionId or scopeId")
+	}
+	err = c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return 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")
+		}
+
+		// Ensure that the primary key value is unique
+		primaryValueStr := ReturnPrimaryKeyValue(structValue.Elem().Interface())
+		if _, ok := keeper[primaryValueStr]; ok {
+			return errors.BadInput.New("duplicated item")
+		} else {
+			keeper[primaryValueStr] = struct{}{}
+		}
+
+		// Set the connection ID, CreatedDate, and UpdatedDate fields
+		SetScopeFields(structValue.Interface(), connectionId, &now, &now)
+
+		// Verify that the primary key value is valid
+		err = VerifyPrimaryKeyValue(structValue.Elem().Interface())
+		if err != nil {
+			return err
+		}
+	}
+
+	// Save the scopes to the database
+	return c.save(scopesValue.Interface(), c.db.Create)
+}
+
+func (c *ScopeApiHelper) Update(input *plugin.ApiResourceInput, fieldName string, connection interface{}, scope interface{}) errors.Error {
+	connectionId, scopeId := ExtractParam(input.Params)
+
+	if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {
+		return errors.BadInput.New("invalid connectionId")
+	}
+	err := c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return err
+	}
+
+	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")
+	}
+	err = DecodeMapStruct(input.Body, scope)
+	if err != nil {
+		return errors.Default.Wrap(err, "patch scope error")
+	}
+	err = VerifyPrimaryKeyValue(scope)
+	if err != nil {
+		return err
+	}
+	err = c.db.Update(scope)
+	if err != nil {
+		return errors.Default.Wrap(err, "error on saving Scope")
+	}
+	return nil
+}
+
+func (c *ScopeApiHelper) GetScopeList(input *plugin.ApiResourceInput, connection interface{}, scopes interface{}, rules interface{}) (map[uint64]string, errors.Error) {
+	connectionId, _ := ExtractParam(input.Params)
+	if connectionId == 0 {
+		return nil, errors.BadInput.New("invalid path params")
+	}
+	err := c.VerifyConnection(connection, 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))
+	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()
+		if ruleId > 0 {
+			ruleIds = append(ruleIds, ruleId)
+		}
+	}
+
+	if len(ruleIds) > 0 {
+		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++ {
+		// 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()
+	}
+	return names, nil
+}
+
+func (c *ScopeApiHelper) GetScope(input *plugin.ApiResourceInput, fieldName string, connection interface{}, scope interface{}, rule interface{}) errors.Error {
+	connectionId, scopeId := ExtractParam(input.Params)
+	if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {
+		return errors.BadInput.New("invalid path params")
+	}
+	err := c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return err
+	}
+	db := c.db
+	query := dal.Where(fmt.Sprintf("connection_id = ? AND %s = ?", fieldName), connectionId, scopeId)
+	err = db.First(scope, query)
+	if db.IsErrorNotFound(err) {
+		return errors.NotFound.New("Scope not found")
+	}
+	if err != nil {
+		return err
+	}
+	repoRuleId := reflect.ValueOf(scope).Elem().FieldByName("TransformationRuleId").Uint()
+	if repoRuleId > 0 {
+		err = db.First(rule, dal.Where("id = ?", repoRuleId))
+		if err != nil {
+			return errors.NotFound.New("transformationRule not found")
+		}
+	}
+	return nil
+}
+
+func (c *ScopeApiHelper) VerifyConnection(connection interface{}, connId uint64) errors.Error {
+	err := c.connHelper.FirstById(&connection, connId)
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return errors.BadInput.New("Invalid Connection Id")
+		}
+		return err
+	}
+	return nil
+}
+
+func (c *ScopeApiHelper) save(scope interface{}, method func(entity interface{}, clauses ...dal.Clause) errors.Error) errors.Error {
+	err := c.db.CreateOrUpdate(scope)
+	if err != nil {
+		if c.db.IsDuplicationError(err) {
+			return errors.BadInput.New("the scope already exists")
+		}
+		return err
+	}
+	return nil
+}
+
+func ExtractParam(params map[string]string) (uint64, string) {

Review Comment:
   This is specifically for extracting fields for `scopeHelper`
   It should NOT be a public method and with this very vague naming.



##########
backend/helpers/pluginhelper/api/scope_helper.go:
##########
@@ -0,0 +1,346 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/go-playground/validator/v10"
+	"github.com/mitchellh/mapstructure"
+	"gorm.io/gorm"
+	"strconv"
+	"strings"
+	"time"
+
+	"reflect"
+)
+
+// ScopeApiHelper is used to write the CURD of connection
+type ScopeApiHelper struct {
+	log        log.Logger
+	db         dal.Dal
+	validator  *validator.Validate
+	connHelper *ConnectionApiHelper
+}
+
+// NewScopeHelper creates a ScopeHelper for connection management
+func NewScopeHelper(
+	basicRes context.BasicRes,
+	vld *validator.Validate,
+	connHelper *ConnectionApiHelper,
+) *ScopeApiHelper {
+	if vld == nil {
+		vld = validator.New()
+	}
+	if connHelper == nil {
+		return nil
+	}
+	return &ScopeApiHelper{
+		log:        basicRes.GetLogger(),
+		db:         basicRes.GetDal(),
+		validator:  vld,
+		connHelper: connHelper,
+	}
+}
+
+// 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")
+	}
+	// 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")
+	}
+	// Extract the connection ID from the input.Params map
+	connectionId, _ := ExtractParam(input.Params)
+	if connectionId == 0 {
+		return errors.BadInput.New("invalid connectionId or scopeId")
+	}
+	err = c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return 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")
+		}
+
+		// Ensure that the primary key value is unique
+		primaryValueStr := ReturnPrimaryKeyValue(structValue.Elem().Interface())
+		if _, ok := keeper[primaryValueStr]; ok {
+			return errors.BadInput.New("duplicated item")
+		} else {
+			keeper[primaryValueStr] = struct{}{}
+		}
+
+		// Set the connection ID, CreatedDate, and UpdatedDate fields
+		SetScopeFields(structValue.Interface(), connectionId, &now, &now)
+
+		// Verify that the primary key value is valid
+		err = VerifyPrimaryKeyValue(structValue.Elem().Interface())
+		if err != nil {
+			return err
+		}
+	}
+
+	// Save the scopes to the database
+	return c.save(scopesValue.Interface(), c.db.Create)
+}
+
+func (c *ScopeApiHelper) Update(input *plugin.ApiResourceInput, fieldName string, connection interface{}, scope interface{}) errors.Error {
+	connectionId, scopeId := ExtractParam(input.Params)
+
+	if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {
+		return errors.BadInput.New("invalid connectionId")
+	}
+	err := c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return err
+	}
+
+	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")
+	}
+	err = DecodeMapStruct(input.Body, scope)
+	if err != nil {
+		return errors.Default.Wrap(err, "patch scope error")
+	}
+	err = VerifyPrimaryKeyValue(scope)
+	if err != nil {
+		return err
+	}
+	err = c.db.Update(scope)
+	if err != nil {
+		return errors.Default.Wrap(err, "error on saving Scope")
+	}
+	return nil
+}
+
+func (c *ScopeApiHelper) GetScopeList(input *plugin.ApiResourceInput, connection interface{}, scopes interface{}, rules interface{}) (map[uint64]string, errors.Error) {
+	connectionId, _ := ExtractParam(input.Params)
+	if connectionId == 0 {
+		return nil, errors.BadInput.New("invalid path params")
+	}
+	err := c.VerifyConnection(connection, 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))
+	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()
+		if ruleId > 0 {
+			ruleIds = append(ruleIds, ruleId)
+		}
+	}
+
+	if len(ruleIds) > 0 {
+		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++ {
+		// 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()
+	}
+	return names, nil
+}
+
+func (c *ScopeApiHelper) GetScope(input *plugin.ApiResourceInput, fieldName string, connection interface{}, scope interface{}, rule interface{}) errors.Error {
+	connectionId, scopeId := ExtractParam(input.Params)
+	if connectionId == 0 || len(scopeId) == 0 || scopeId == "0" {
+		return errors.BadInput.New("invalid path params")
+	}
+	err := c.VerifyConnection(connection, connectionId)
+	if err != nil {
+		return err
+	}
+	db := c.db
+	query := dal.Where(fmt.Sprintf("connection_id = ? AND %s = ?", fieldName), connectionId, scopeId)
+	err = db.First(scope, query)
+	if db.IsErrorNotFound(err) {
+		return errors.NotFound.New("Scope not found")
+	}
+	if err != nil {
+		return err
+	}
+	repoRuleId := reflect.ValueOf(scope).Elem().FieldByName("TransformationRuleId").Uint()
+	if repoRuleId > 0 {
+		err = db.First(rule, dal.Where("id = ?", repoRuleId))
+		if err != nil {
+			return errors.NotFound.New("transformationRule not found")
+		}
+	}
+	return nil
+}
+
+func (c *ScopeApiHelper) VerifyConnection(connection interface{}, connId uint64) errors.Error {
+	err := c.connHelper.FirstById(&connection, connId)
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return errors.BadInput.New("Invalid Connection Id")
+		}
+		return err
+	}
+	return nil
+}
+
+func (c *ScopeApiHelper) save(scope interface{}, method func(entity interface{}, clauses ...dal.Clause) errors.Error) errors.Error {
+	err := c.db.CreateOrUpdate(scope)
+	if err != nil {
+		if c.db.IsDuplicationError(err) {
+			return errors.BadInput.New("the scope already exists")
+		}
+		return err
+	}
+	return nil
+}
+
+func ExtractParam(params map[string]string) (uint64, string) {
+	connectionId, err := strconv.ParseUint(params["connectionId"], 10, 64)
+	if err != nil {
+		return 0, ""
+	}
+	scopeId := params["scopeId"]
+	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 {

Review Comment:
   I don't think it is working correctly, the primary keys are most likely nested inside fields, consider using instead https://github.com/apache/incubator-devlake/blob/243cc8a80aa5b37828e2a142ac9f7e3269b7e1dc/backend/core/utils/structfield.go#L25 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org