You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2023/03/30 07:43:49 UTC

[incubator-devlake] branch main updated: feat: add prepareFirstToken api; update tr helper decode type (#4796)

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

warren 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 5f89c2118 feat: add prepareFirstToken api; update tr helper decode type (#4796)
5f89c2118 is described below

commit 5f89c21181ca49cd10253078665a9124e030469a
Author: Likyh <ya...@meri.co>
AuthorDate: Thu Mar 30 15:43:44 2023 +0800

    feat: add prepareFirstToken api; update tr helper decode type (#4796)
    
    * feat: add prepareFirstToken api; update tr helper decode type
    
    * fix: update for review
---
 backend/core/plugin/plugin_connection_abstract.go  |  7 ---
 .../helpers/pluginhelper/api/iso8601time_test.go   |  6 +-
 backend/helpers/pluginhelper/api/mapstructure.go   | 70 +++++++++++-----------
 .../helpers/pluginhelper/api/mapstructure_test.go  |  2 +-
 .../helpers/pluginhelper/api/remote_api_helper.go  | 45 +++++++++++---
 backend/helpers/pluginhelper/api/scope_helper.go   |  4 +-
 .../pluginhelper/api/transformation_rule_helper.go | 16 ++---
 backend/plugins/bamboo/api/remote.go               |  6 +-
 backend/plugins/bitbucket/api/remote.go            |  8 +--
 backend/plugins/gitlab/api/remote.go               |  8 +--
 backend/plugins/jira/api/transformation_rule.go    |  2 +-
 backend/plugins/sonarqube/api/remote.go            |  6 +-
 backend/plugins/trello/api/scope.go                |  2 +-
 backend/plugins/trello/api/transformation_rule.go  |  2 +-
 backend/plugins/webhook/api/cicd_pipeline.go       |  4 +-
 backend/plugins/webhook/api/issue.go               |  2 +-
 backend/plugins/zentao/api/remote.go               |  8 +--
 backend/server/services/blueprint.go               |  2 +-
 backend/server/services/project.go                 |  2 +-
 19 files changed, 113 insertions(+), 89 deletions(-)

diff --git a/backend/core/plugin/plugin_connection_abstract.go b/backend/core/plugin/plugin_connection_abstract.go
index 157c63df0..6a9b648c8 100644
--- a/backend/core/plugin/plugin_connection_abstract.go
+++ b/backend/core/plugin/plugin_connection_abstract.go
@@ -31,13 +31,6 @@ type ApiConnection interface {
 	GetRateLimitPerHour() int
 }
 
-type QueryData struct {
-	Page    int    `json:"page"`
-	PerPage int    `json:"per_page"`
-	Tag     string `json:"tag"`
-	Search  []string
-}
-
 // ApiAuthenticator is to be implemented by a Concreate Connection if Authorization is required
 type ApiAuthenticator interface {
 	// SetupAuthentication is a hook function for connection to set up authentication for the HTTP request
diff --git a/backend/helpers/pluginhelper/api/iso8601time_test.go b/backend/helpers/pluginhelper/api/iso8601time_test.go
index bc7f9603e..37ba0af88 100644
--- a/backend/helpers/pluginhelper/api/iso8601time_test.go
+++ b/backend/helpers/pluginhelper/api/iso8601time_test.go
@@ -70,17 +70,17 @@ func TestIso8601Time(t *testing.T) {
 		assert.Nil(t, err)
 
 		var record2 Iso8601TimeRecord
-		err = DecodeMapStruct(ms, &record2)
+		err = DecodeMapStruct(ms, &record2, true)
 		assert.Nil(t, err)
 		assert.Equal(t, expected, record2.Created.ToTime().UTC())
 
 		var record3 Iso8601TimeRecordP
-		err = DecodeMapStruct(ms, &record3)
+		err = DecodeMapStruct(ms, &record3, true)
 		assert.Nil(t, err)
 		assert.Equal(t, expected, record3.Created.ToTime().UTC())
 
 		var record4 TimeRecord
-		err = DecodeMapStruct(ms, &record4)
+		err = DecodeMapStruct(ms, &record4, true)
 		assert.Nil(t, err)
 		assert.Equal(t, expected, record4.Created.UTC())
 	}
diff --git a/backend/helpers/pluginhelper/api/mapstructure.go b/backend/helpers/pluginhelper/api/mapstructure.go
index 4ecbc9e6f..ba93a5c84 100644
--- a/backend/helpers/pluginhelper/api/mapstructure.go
+++ b/backend/helpers/pluginhelper/api/mapstructure.go
@@ -29,45 +29,45 @@ import (
 	"github.com/mitchellh/mapstructure"
 )
 
-// DecodeMapStruct with time.Time and Iso8601Time support
-func DecodeMapStruct(input map[string]interface{}, result interface{}) errors.Error {
-	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
-		ZeroFields: true,
-		DecodeHook: mapstructure.ComposeDecodeHookFunc(
-			func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
-				if data == nil {
-					return nil, nil
-				}
-				if t == reflect.TypeOf(json.RawMessage{}) {
-					return json.Marshal(data)
-				}
+func DecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
+	if data == nil {
+		return nil, nil
+	}
+	if t == reflect.TypeOf(json.RawMessage{}) {
+		return json.Marshal(data)
+	}
 
-				if t != reflect.TypeOf(Iso8601Time{}) && t != reflect.TypeOf(time.Time{}) {
-					return data, nil
-				}
+	if t != reflect.TypeOf(Iso8601Time{}) && t != reflect.TypeOf(time.Time{}) {
+		return data, nil
+	}
 
-				var tt time.Time
-				var err error
+	var tt time.Time
+	var err error
 
-				switch f.Kind() {
-				case reflect.String:
-					tt, err = ConvertStringToTime(data.(string))
-				case reflect.Float64:
-					tt = time.Unix(0, int64(data.(float64))*int64(time.Millisecond))
-				case reflect.Int64:
-					tt = time.Unix(0, data.(int64)*int64(time.Millisecond))
-				}
-				if err != nil {
-					return data, nil
-				}
+	switch f.Kind() {
+	case reflect.String:
+		tt, err = ConvertStringToTime(data.(string))
+	case reflect.Float64:
+		tt = time.Unix(0, int64(data.(float64))*int64(time.Millisecond))
+	case reflect.Int64:
+		tt = time.Unix(0, data.(int64)*int64(time.Millisecond))
+	}
+	if err != nil {
+		return data, nil
+	}
+
+	if t == reflect.TypeOf(Iso8601Time{}) {
+		return Iso8601Time{time: tt}, nil
+	}
+	return tt, nil
+}
 
-				if t == reflect.TypeOf(Iso8601Time{}) {
-					return Iso8601Time{time: tt}, nil
-				}
-				return tt, nil
-			},
-		),
-		Result: result,
+// DecodeMapStruct with time.Time and Iso8601Time support
+func DecodeMapStruct(input map[string]interface{}, result interface{}, zeroFields bool) errors.Error {
+	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+		ZeroFields: zeroFields,
+		DecodeHook: mapstructure.ComposeDecodeHookFunc(DecodeHook),
+		Result:     result,
 	})
 	if err != nil {
 		return errors.Convert(err)
diff --git a/backend/helpers/pluginhelper/api/mapstructure_test.go b/backend/helpers/pluginhelper/api/mapstructure_test.go
index dfb4fe716..ff02761a8 100644
--- a/backend/helpers/pluginhelper/api/mapstructure_test.go
+++ b/backend/helpers/pluginhelper/api/mapstructure_test.go
@@ -45,7 +45,7 @@ func TestDecodeMapStructJsonRawMessage(t *testing.T) {
 		Settings: json.RawMessage(`{"version": "1.0.101"}`),
 		Existing: json.RawMessage(`{"hello", "world"}`),
 	}
-	err := DecodeMapStruct(input, decoded)
+	err := DecodeMapStruct(input, decoded, true)
 	fmt.Println(string(decoded.Settings))
 	assert.Nil(t, err)
 	assert.Equal(t, decoded.Id, 100)
diff --git a/backend/helpers/pluginhelper/api/remote_api_helper.go b/backend/helpers/pluginhelper/api/remote_api_helper.go
index 64840fe4b..a34457e38 100644
--- a/backend/helpers/pluginhelper/api/remote_api_helper.go
+++ b/backend/helpers/pluginhelper/api/remote_api_helper.go
@@ -37,6 +37,18 @@ type RemoteScopesChild struct {
 	Data     interface{} `json:"data"`
 }
 
+type RemoteQueryData struct {
+	Page       int    `json:"page"`
+	PerPage    int    `json:"per_page"`
+	CustomInfo string `json:"custom"`
+	Tag        string `json:"tag"`
+	Search     []string
+}
+
+type FirstPageTokenOutput struct {
+	PageToken string `json:"pageToken"`
+}
+
 type RemoteScopesOutput struct {
 	Children      []RemoteScopesChild `json:"children"`
 	NextPageToken string              `json:"nextPageToken"`
@@ -102,9 +114,26 @@ const remoteScopesPerPage int = 100
 const TypeProject string = "scope"
 const TypeGroup string = "group"
 
+// PrepareFirstPageToken prepares the first page token
+func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) PrepareFirstPageToken(customInfo string) (*plugin.ApiResourceOutput, errors.Error) {
+	outputBody := &FirstPageTokenOutput{}
+	pageToken, err := getPageTokenFromPageData(&RemoteQueryData{
+		Page:       1,
+		PerPage:    remoteScopesPerPage,
+		CustomInfo: customInfo,
+		Tag:        "group",
+	})
+	if err != nil {
+		return nil, err
+	}
+	outputBody.PageToken = pageToken
+	return &plugin.ApiResourceOutput{Body: outputBody, Status: http.StatusOK}, nil
+}
+
+// GetScopesFromRemote gets the scopes from api
 func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) GetScopesFromRemote(input *plugin.ApiResourceInput,
-	getGroup func(basicRes coreContext.BasicRes, gid string, queryData *plugin.QueryData, connection Conn) ([]Group, errors.Error),
-	getScope func(basicRes coreContext.BasicRes, gid string, queryData *plugin.QueryData, connection Conn) ([]ApiScope, errors.Error),
+	getGroup func(basicRes coreContext.BasicRes, gid string, queryData *RemoteQueryData, connection Conn) ([]Group, errors.Error),
+	getScope func(basicRes coreContext.BasicRes, gid string, queryData *RemoteQueryData, connection Conn) ([]ApiScope, errors.Error),
 ) (*plugin.ApiResourceOutput, errors.Error) {
 	connectionId, _ := extractFromReqParam(input.Params)
 	if connectionId == 0 {
@@ -215,7 +244,7 @@ func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) GetScopesFromRemote(inpu
 	return &plugin.ApiResourceOutput{Body: outputBody, Status: http.StatusOK}, nil
 }
 
-func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) SearchRemoteScopes(input *plugin.ApiResourceInput, searchScope func(basicRes coreContext.BasicRes, queryData *plugin.QueryData, connection Conn) ([]ApiScope, errors.Error)) (*plugin.ApiResourceOutput, errors.Error) {
+func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) SearchRemoteScopes(input *plugin.ApiResourceInput, searchScope func(basicRes coreContext.BasicRes, queryData *RemoteQueryData, connection Conn) ([]ApiScope, errors.Error)) (*plugin.ApiResourceOutput, errors.Error) {
 	connectionId, _ := extractFromReqParam(input.Params)
 	if connectionId == 0 {
 		return nil, errors.BadInput.New("invalid connectionId")
@@ -254,7 +283,7 @@ func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) SearchRemoteScopes(input
 		}
 	}
 
-	queryData := &plugin.QueryData{
+	queryData := &RemoteQueryData{
 		Page:    p,
 		PerPage: ps,
 		Search:  search,
@@ -288,7 +317,7 @@ func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) SearchRemoteScopes(input
 	return &plugin.ApiResourceOutput{Body: outputBody, Status: http.StatusOK}, nil
 }
 
-func getPageTokenFromPageData(pageData *plugin.QueryData) (string, errors.Error) {
+func getPageTokenFromPageData(pageData *RemoteQueryData) (string, errors.Error) {
 	// Marshal json
 	pageTokenDecode, err := json.Marshal(pageData)
 	if err != nil {
@@ -299,9 +328,9 @@ func getPageTokenFromPageData(pageData *plugin.QueryData) (string, errors.Error)
 	return base64.StdEncoding.EncodeToString(pageTokenDecode), nil
 }
 
-func getPageDataFromPageToken(pageToken string) (*plugin.QueryData, errors.Error) {
+func getPageDataFromPageToken(pageToken string) (*RemoteQueryData, errors.Error) {
 	if pageToken == "" {
-		return &plugin.QueryData{
+		return &RemoteQueryData{
 			Page:    1,
 			PerPage: remoteScopesPerPage,
 			Tag:     "group",
@@ -314,7 +343,7 @@ func getPageDataFromPageToken(pageToken string) (*plugin.QueryData, errors.Error
 		return nil, errors.Default.Wrap(err, fmt.Sprintf("decode pageToken failed %s", pageToken))
 	}
 	// Unmarshal json
-	pt := &plugin.QueryData{}
+	pt := &RemoteQueryData{}
 	err = json.Unmarshal(pageTokenDecode, pt)
 	if err != nil {
 		return nil, errors.Default.Wrap(err, fmt.Sprintf("json Unmarshal pageTokenDecode failed %s", pageTokenDecode))
diff --git a/backend/helpers/pluginhelper/api/scope_helper.go b/backend/helpers/pluginhelper/api/scope_helper.go
index 74923bf90..a47f1ec53 100644
--- a/backend/helpers/pluginhelper/api/scope_helper.go
+++ b/backend/helpers/pluginhelper/api/scope_helper.go
@@ -81,7 +81,7 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) Put(input *plugin.ApiResourceInput) (*
 	var req struct {
 		Data []*Scope `json:"data"`
 	}
-	err := errors.Convert(DecodeMapStruct(input.Body, &req))
+	err := errors.Convert(DecodeMapStruct(input.Body, &req, true))
 	if err != nil {
 		return nil, errors.BadInput.Wrap(err, "decoding scope error")
 	}
@@ -143,7 +143,7 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) Update(input *plugin.ApiResourceInput,
 	if err != nil {
 		return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusInternalServerError}, errors.Default.New("getting Scope error")
 	}
-	err = DecodeMapStruct(input.Body, &scope)
+	err = DecodeMapStruct(input.Body, &scope, true)
 	if err != nil {
 		return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusInternalServerError}, errors.Default.Wrap(err, "patch scope error")
 	}
diff --git a/backend/helpers/pluginhelper/api/transformation_rule_helper.go b/backend/helpers/pluginhelper/api/transformation_rule_helper.go
index 003982218..d4aa52ba8 100644
--- a/backend/helpers/pluginhelper/api/transformation_rule_helper.go
+++ b/backend/helpers/pluginhelper/api/transformation_rule_helper.go
@@ -28,7 +28,6 @@ import (
 	"github.com/apache/incubator-devlake/core/log"
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/go-playground/validator/v10"
-	"github.com/mitchellh/mapstructure"
 )
 
 // TransformationRuleHelper is used to write the CURD of transformation rule
@@ -59,17 +58,20 @@ func (t TransformationRuleHelper[Tr]) Create(input *plugin.ApiResourceInput) (*p
 		return nil, errors.Default.Wrap(e, "the connection ID should be an non-zero integer")
 	}
 	var rule Tr
-	err := Decode(input.Body, &rule, t.validator)
-	if err != nil {
-		return nil, errors.BadInput.Wrap(err, "error in decoding transformation rule")
+	if err := DecodeMapStruct(input.Body, &rule, false); err != nil {
+		return nil, errors.Default.Wrap(err, "error in decoding transformation rule")
+	}
+	if t.validator != nil {
+		if err := t.validator.Struct(rule); err != nil {
+			return nil, errors.Default.Wrap(err, "error validating transformation rule")
+		}
 	}
 	valueConnectionId := reflect.ValueOf(&rule).Elem().FieldByName("ConnectionId")
 	if valueConnectionId.IsValid() {
 		valueConnectionId.SetUint(connectionId)
 	}
 
-	err = t.db.Create(&rule)
-	if err != nil {
+	if err := t.db.Create(&rule); err != nil {
 		if t.db.IsDuplicationError(err) {
 			return nil, errors.BadInput.New("there was a transformation rule with the same name, please choose another name")
 		}
@@ -88,7 +90,7 @@ func (t TransformationRuleHelper[Tr]) Update(input *plugin.ApiResourceInput) (*p
 	if err != nil {
 		return nil, errors.Default.Wrap(err, "error on saving TransformationRule")
 	}
-	err = errors.Convert(mapstructure.Decode(input.Body, &old))
+	err = DecodeMapStruct(input.Body, &old, false)
 	if err != nil {
 		return nil, errors.Default.Wrap(err, "error decoding map into transformationRule")
 	}
diff --git a/backend/plugins/bamboo/api/remote.go b/backend/plugins/bamboo/api/remote.go
index a70b181bc..7e1e8bbfe 100644
--- a/backend/plugins/bamboo/api/remote.go
+++ b/backend/plugins/bamboo/api/remote.go
@@ -45,7 +45,7 @@ import (
 func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.GetScopesFromRemote(input,
 		nil,
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.BambooConnection) ([]models.ApiBambooProject, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.BambooConnection) ([]models.ApiBambooProject, errors.Error) {
 			query := initialQuery(queryData)
 			// create api client
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
@@ -82,7 +82,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 // @Router /plugins/bamboo/connections/{connectionId}/search-remote-scopes [GET]
 func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.SearchRemoteScopes(input,
-		func(basicRes context2.BasicRes, queryData *plugin.QueryData, connection models.BambooConnection) ([]models.ApiBambooProject, errors.Error) {
+		func(basicRes context2.BasicRes, queryData *api.RemoteQueryData, connection models.BambooConnection) ([]models.ApiBambooProject, errors.Error) {
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
 			if err != nil {
 				return nil, errors.BadInput.Wrap(err, "failed to get create apiClient")
@@ -113,7 +113,7 @@ func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutp
 		})
 }
 
-func initialQuery(queryData *plugin.QueryData) url.Values {
+func initialQuery(queryData *api.RemoteQueryData) url.Values {
 	query := url.Values{}
 	query.Set("showEmpty", fmt.Sprintf("%v", true))
 	query.Set("max-result", fmt.Sprintf("%v", queryData.PerPage))
diff --git a/backend/plugins/bitbucket/api/remote.go b/backend/plugins/bitbucket/api/remote.go
index 68df6e4f1..3d52732a8 100644
--- a/backend/plugins/bitbucket/api/remote.go
+++ b/backend/plugins/bitbucket/api/remote.go
@@ -44,7 +44,7 @@ import (
 // @Router /plugins/bitbucket/connections/{connectionId}/remote-scopes [GET]
 func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.GetScopesFromRemote(input,
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.BitbucketConnection) ([]models.GroupResponse, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.BitbucketConnection) ([]models.GroupResponse, errors.Error) {
 			if gid != "" {
 				return nil, nil
 			}
@@ -70,7 +70,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 
 			return resBody.Values, err
 		},
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.BitbucketConnection) ([]models.BitbucketApiRepo, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.BitbucketConnection) ([]models.BitbucketApiRepo, errors.Error) {
 			if gid == "" {
 				return nil, nil
 			}
@@ -112,7 +112,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 // @Router /plugins/bitbucket/connections/{connectionId}/search-remote-scopes [GET]
 func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.SearchRemoteScopes(input,
-		func(basicRes context2.BasicRes, queryData *plugin.QueryData, connection models.BitbucketConnection) ([]models.BitbucketApiRepo, errors.Error) {
+		func(basicRes context2.BasicRes, queryData *api.RemoteQueryData, connection models.BitbucketConnection) ([]models.BitbucketApiRepo, errors.Error) {
 			// create api client
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
 			if err != nil {
@@ -145,7 +145,7 @@ func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutp
 	)
 }
 
-func initialQuery(queryData *plugin.QueryData) url.Values {
+func initialQuery(queryData *api.RemoteQueryData) url.Values {
 	query := url.Values{}
 	query.Set("page", fmt.Sprintf("%v", queryData.Page))
 	query.Set("pagelen", fmt.Sprintf("%v", queryData.PerPage))
diff --git a/backend/plugins/gitlab/api/remote.go b/backend/plugins/gitlab/api/remote.go
index 62c950834..16916ce05 100644
--- a/backend/plugins/gitlab/api/remote.go
+++ b/backend/plugins/gitlab/api/remote.go
@@ -43,7 +43,7 @@ import (
 // @Router /plugins/gitlab/connections/{connectionId}/remote-scopes [GET]
 func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.GetScopesFromRemote(input,
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.GitlabConnection) ([]models.GroupResponse, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.GitlabConnection) ([]models.GroupResponse, errors.Error) {
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
 			if err != nil {
 				return nil, errors.BadInput.Wrap(err, "failed to get create apiClient")
@@ -69,7 +69,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 			}
 			return resBody, err
 		},
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.GitlabConnection) ([]models.GitlabApiProject, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.GitlabConnection) ([]models.GitlabApiProject, errors.Error) {
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
 			if err != nil {
 				return nil, errors.BadInput.Wrap(err, "failed to get create apiClient")
@@ -112,7 +112,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 // @Router /plugins/gitlab/connections/{connectionId}/search-remote-scopes [GET]
 func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.SearchRemoteScopes(input,
-		func(basicRes context2.BasicRes, queryData *plugin.QueryData, connection models.GitlabConnection) ([]models.GitlabApiProject, errors.Error) {
+		func(basicRes context2.BasicRes, queryData *api.RemoteQueryData, connection models.GitlabConnection) ([]models.GitlabApiProject, errors.Error) {
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
 			if err != nil {
 				return nil, errors.BadInput.Wrap(err, "failed to get create apiClient")
@@ -138,7 +138,7 @@ func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutp
 		})
 }
 
-func initialQuery(queryData *plugin.QueryData) url.Values {
+func initialQuery(queryData *api.RemoteQueryData) url.Values {
 	query := url.Values{}
 	query.Set("page", fmt.Sprintf("%v", queryData.Page))
 	query.Set("per_page", fmt.Sprintf("%v", queryData.PerPage))
diff --git a/backend/plugins/jira/api/transformation_rule.go b/backend/plugins/jira/api/transformation_rule.go
index f3fffe97c..cf3899edc 100644
--- a/backend/plugins/jira/api/transformation_rule.go
+++ b/backend/plugins/jira/api/transformation_rule.go
@@ -90,7 +90,7 @@ func UpdateTransformationRule(input *plugin.ApiResourceInput) (*plugin.ApiResour
 	if err != nil {
 		return nil, err
 	}
-	err = api.DecodeMapStruct(input.Body, oldTr)
+	err = api.DecodeMapStruct(input.Body, oldTr, true)
 	if err != nil {
 		return nil, err
 	}
diff --git a/backend/plugins/sonarqube/api/remote.go b/backend/plugins/sonarqube/api/remote.go
index 8f0be7030..5e29e4615 100644
--- a/backend/plugins/sonarqube/api/remote.go
+++ b/backend/plugins/sonarqube/api/remote.go
@@ -42,7 +42,7 @@ import (
 func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.GetScopesFromRemote(input,
 		nil,
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.SonarqubeConnection) ([]models.SonarqubeApiProject, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.SonarqubeConnection) ([]models.SonarqubeApiProject, errors.Error) {
 			query := initialQuery(queryData)
 			// create api client
 			apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
@@ -82,7 +82,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	return remoteHelper.GetScopesFromRemote(input,
 		nil,
-		func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.SonarqubeConnection) ([]models.SonarqubeApiProject, errors.Error) {
+		func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.SonarqubeConnection) ([]models.SonarqubeApiProject, errors.Error) {
 			query := initialQuery(queryData)
 			query.Set("q", queryData.Search[0])
 			// create api client
@@ -108,7 +108,7 @@ func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutp
 		})
 }
 
-func initialQuery(queryData *plugin.QueryData) url.Values {
+func initialQuery(queryData *api.RemoteQueryData) url.Values {
 	query := url.Values{}
 	query.Set("p", fmt.Sprintf("%v", queryData.Page))
 	query.Set("ps", fmt.Sprintf("%v", queryData.PerPage))
diff --git a/backend/plugins/trello/api/scope.go b/backend/plugins/trello/api/scope.go
index 01cf780d1..7f9675780 100644
--- a/backend/plugins/trello/api/scope.go
+++ b/backend/plugins/trello/api/scope.go
@@ -102,7 +102,7 @@ func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, err
 	if err != nil {
 		return nil, errors.Default.Wrap(err, "getting TrelloBoard error")
 	}
-	err = api.DecodeMapStruct(input.Body, &board)
+	err = api.DecodeMapStruct(input.Body, &board, true)
 	if err != nil {
 		return nil, errors.Default.Wrap(err, "patch trello board error")
 	}
diff --git a/backend/plugins/trello/api/transformation_rule.go b/backend/plugins/trello/api/transformation_rule.go
index c47b1ca1b..ac8bed828 100644
--- a/backend/plugins/trello/api/transformation_rule.go
+++ b/backend/plugins/trello/api/transformation_rule.go
@@ -74,7 +74,7 @@ func UpdateTransformationRule(input *plugin.ApiResourceInput) (*plugin.ApiResour
 	if err != nil {
 		return nil, errors.Default.Wrap(err, "error on saving TransformationRule")
 	}
-	err = api.DecodeMapStruct(input.Body, &old)
+	err = api.DecodeMapStruct(input.Body, &old, true)
 	if err != nil {
 		return nil, errors.Default.Wrap(err, "error decoding map into transformationRule")
 	}
diff --git a/backend/plugins/webhook/api/cicd_pipeline.go b/backend/plugins/webhook/api/cicd_pipeline.go
index b81b982eb..c4708d20b 100644
--- a/backend/plugins/webhook/api/cicd_pipeline.go
+++ b/backend/plugins/webhook/api/cicd_pipeline.go
@@ -76,7 +76,7 @@ func PostCicdTask(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 	}
 	// get request
 	request := &WebhookTaskRequest{}
-	err = api.DecodeMapStruct(input.Body, request)
+	err = api.DecodeMapStruct(input.Body, request, true)
 	if err != nil {
 		return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil
 	}
@@ -287,7 +287,7 @@ func PostDeploymentCicdTask(input *plugin.ApiResourceInput) (*plugin.ApiResource
 	}
 	// get request
 	request := &WebhookDeployTaskRequest{}
-	err = api.DecodeMapStruct(input.Body, request)
+	err = api.DecodeMapStruct(input.Body, request, true)
 	if err != nil {
 		return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil
 	}
diff --git a/backend/plugins/webhook/api/issue.go b/backend/plugins/webhook/api/issue.go
index 9bde3a1a0..5367c5713 100644
--- a/backend/plugins/webhook/api/issue.go
+++ b/backend/plugins/webhook/api/issue.go
@@ -79,7 +79,7 @@ func PostIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, error
 	}
 	// get request
 	request := &WebhookIssueRequest{}
-	err = helper.DecodeMapStruct(input.Body, request)
+	err = helper.DecodeMapStruct(input.Body, request, true)
 	if err != nil {
 		return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil
 	}
diff --git a/backend/plugins/zentao/api/remote.go b/backend/plugins/zentao/api/remote.go
index 9152f5b18..25fc6d35b 100644
--- a/backend/plugins/zentao/api/remote.go
+++ b/backend/plugins/zentao/api/remote.go
@@ -43,7 +43,7 @@ type ProjectResponse struct {
 	Values []models.ZentaoProject `json:"projects"`
 }
 
-func getGroup(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.ZentaoConnection) ([]api.BaseRemoteGroupResponse, errors.Error) {
+func getGroup(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.ZentaoConnection) ([]api.BaseRemoteGroupResponse, errors.Error) {
 	return []api.BaseRemoteGroupResponse{
 		{
 			Id:   `products`,
@@ -79,7 +79,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 	} else if gid == `products` {
 		return productRemoteHelper.GetScopesFromRemote(input,
 			nil,
-			func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.ZentaoConnection) ([]models.ZentaoProductRes, errors.Error) {
+			func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.ZentaoConnection) ([]models.ZentaoProductRes, errors.Error) {
 				query := initialQuery(queryData)
 				// create api client
 				apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
@@ -104,7 +104,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 	} else if gid == `projects` {
 		return projectRemoteHelper.GetScopesFromRemote(input,
 			nil,
-			func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.ZentaoConnection) ([]models.ZentaoProject, errors.Error) {
+			func(basicRes context2.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.ZentaoConnection) ([]models.ZentaoProject, errors.Error) {
 				query := initialQuery(queryData)
 				// create api client
 				apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
@@ -130,7 +130,7 @@ func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 	return nil, nil
 }
 
-func initialQuery(queryData *plugin.QueryData) url.Values {
+func initialQuery(queryData *api.RemoteQueryData) url.Values {
 	query := url.Values{}
 	query.Set("page", fmt.Sprintf("%v", queryData.Page))
 	query.Set("limit", fmt.Sprintf("%v", queryData.PerPage))
diff --git a/backend/server/services/blueprint.go b/backend/server/services/blueprint.go
index 7b24b4814..aec8a22cc 100644
--- a/backend/server/services/blueprint.go
+++ b/backend/server/services/blueprint.go
@@ -200,7 +200,7 @@ func PatchBlueprint(id uint64, body map[string]interface{}) (*models.Blueprint,
 	}
 
 	originMode := blueprint.Mode
-	err = helper.DecodeMapStruct(body, blueprint)
+	err = helper.DecodeMapStruct(body, blueprint, true)
 	if err != nil {
 		return nil, err
 	}
diff --git a/backend/server/services/project.go b/backend/server/services/project.go
index f57c82de0..64188e7ca 100644
--- a/backend/server/services/project.go
+++ b/backend/server/services/project.go
@@ -133,7 +133,7 @@ func PatchProject(name string, body map[string]interface{}) (*models.ApiOutputPr
 	projectInput := &models.ApiInputProject{}
 
 	// load input
-	err := helper.DecodeMapStruct(body, projectInput)
+	err := helper.DecodeMapStruct(body, projectInput, true)
 	if err != nil {
 		return nil, err
 	}