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 2571e57f6 feat: add connection id to transformation rules (#4775)
2571e57f6 is described below

commit 2571e57f61fcfadf9f33dc8063cf1dd167d518a5
Author: Liang Zhang <[email protected]>
AuthorDate: Mon Mar 27 13:57:45 2023 +0800

    feat: add connection id to transformation rules (#4775)
    
    * feat: add connection id to transformation rules
    
    * fix: gofmt
---
 .../pluginhelper/api/transformation_rule_helper.go | 21 +++++-
 backend/plugins/bamboo/api/transformation_rule.go  | 12 ++--
 backend/plugins/bamboo/impl/impl.go                |  4 +-
 ...22_add_connection_id_to_transformation_rules.go | 73 +++++++++++++++++++++
 .../bamboo/models/migrationscripts/register.go     |  1 +
 .../plugins/bamboo/models/transformation_rule.go   |  3 +-
 .../plugins/bitbucket/api/transformation_rule.go   | 12 ++--
 backend/plugins/bitbucket/impl/impl.go             |  4 +-
 ...22_add_connection_id_to_transformation_rules.go | 74 ++++++++++++++++++++++
 .../bitbucket/models/migrationscripts/register.go  |  1 +
 .../bitbucket/models/transformation_rule.go        |  1 +
 backend/plugins/github/api/transformation_rule.go  | 12 ++--
 backend/plugins/github/impl/impl.go                |  4 +-
 ...22_add_connection_id_to_transformation_rules.go | 74 ++++++++++++++++++++++
 .../github/models/migrationscripts/register.go     |  1 +
 .../plugins/github/models/transformation_rule.go   |  1 +
 backend/plugins/gitlab/api/transformation_rule.go  | 12 ++--
 backend/plugins/gitlab/impl/impl.go                |  4 +-
 ...22_add_connection_id_to_transformation_rules.go | 74 ++++++++++++++++++++++
 .../gitlab/models/migrationscripts/register.go     |  1 +
 .../plugins/gitlab/models/transformation_rule.go   |  1 +
 backend/plugins/jenkins/api/transformation_rule.go | 12 ++--
 backend/plugins/jenkins/impl/impl.go               |  4 +-
 ...22_add_connection_id_to_transformation_rules.go | 74 ++++++++++++++++++++++
 .../jenkins/models/migrationscripts/register.go    |  1 +
 .../plugins/jenkins/models/transformation_rule.go  |  1 +
 backend/plugins/jira/api/transformation_rule.go    | 62 +++++++++++++-----
 backend/plugins/jira/impl/impl.go                  |  4 +-
 ...22_add_connection_id_to_transformation_rules.go | 74 ++++++++++++++++++++++
 .../jira/models/migrationscripts/register.go       |  1 +
 .../plugins/jira/models/transformation_rules.go    |  1 +
 backend/plugins/jira/tasks/task_data.go            |  3 +
 32 files changed, 576 insertions(+), 51 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/transformation_rule_helper.go 
b/backend/helpers/pluginhelper/api/transformation_rule_helper.go
index 645cc1385..003982218 100644
--- a/backend/helpers/pluginhelper/api/transformation_rule_helper.go
+++ b/backend/helpers/pluginhelper/api/transformation_rule_helper.go
@@ -18,6 +18,10 @@ limitations under the License.
 package api
 
 import (
+       "net/http"
+       "reflect"
+       "strconv"
+
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
@@ -25,8 +29,6 @@ import (
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/go-playground/validator/v10"
        "github.com/mitchellh/mapstructure"
-       "net/http"
-       "strconv"
 )
 
 // TransformationRuleHelper is used to write the CURD of transformation rule
@@ -52,11 +54,20 @@ func NewTransformationRuleHelper[Tr dal.Tabler](
 }
 
 func (t TransformationRuleHelper[Tr]) Create(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       connectionId, e := strconv.ParseUint(input.Params["connectionId"], 10, 
64)
+       if e != nil || connectionId == 0 {
+               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")
        }
+       valueConnectionId := 
reflect.ValueOf(&rule).Elem().FieldByName("ConnectionId")
+       if valueConnectionId.IsValid() {
+               valueConnectionId.SetUint(connectionId)
+       }
+
        err = t.db.Create(&rule)
        if err != nil {
                if t.db.IsDuplicationError(err) {
@@ -105,9 +116,13 @@ func (t TransformationRuleHelper[Tr]) Get(input 
*plugin.ApiResourceInput) (*plug
 }
 
 func (t TransformationRuleHelper[Tr]) List(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       connectionId, e := strconv.ParseUint(input.Params["connectionId"], 10, 
64)
+       if e != nil || connectionId == 0 {
+               return nil, errors.Default.Wrap(e, "the connection ID should be 
an non-zero integer")
+       }
        var rules []Tr
        limit, offset := GetLimitOffset(input.Query, "pageSize", "page")
-       err := t.db.All(&rules, dal.Limit(limit), dal.Offset(offset))
+       err := t.db.All(&rules, dal.Where("connection_id = ?", connectionId), 
dal.Limit(limit), dal.Offset(offset))
        if err != nil {
                return nil, errors.Default.Wrap(err, "error on get 
TransformationRule list")
        }
diff --git a/backend/plugins/bamboo/api/transformation_rule.go 
b/backend/plugins/bamboo/api/transformation_rule.go
index 8e833e9c2..99e6812e5 100644
--- a/backend/plugins/bamboo/api/transformation_rule.go
+++ b/backend/plugins/bamboo/api/transformation_rule.go
@@ -27,11 +27,12 @@ import (
 // @Description create transformation rule for Bamboo
 // @Tags plugins/bamboo
 // @Accept application/json
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.BambooTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.BambooTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/transformation_rules [POST]
+// @Router /plugins/bamboo/connections/{connectionId}/transformation_rules 
[POST]
 func CreateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Create(input)
 }
@@ -43,10 +44,11 @@ func CreateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Accept application/json
 // @Param id path int true "id"
 // @Param transformationRule body models.BambooTransformationRule true 
"transformation rule"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.BambooTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/transformation_rules/{id} [PATCH]
+// @Router 
/plugins/bamboo/connections/{connectionId}/transformation_rules/{id} [PATCH]
 func UpdateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Update(input)
 }
@@ -56,10 +58,11 @@ func UpdateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Description return one transformation rule
 // @Tags plugins/bamboo
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.BambooTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/transformation_rules/{id} [GET]
+// @Router 
/plugins/bamboo/connections/{connectionId}/transformation_rules/{id} [GET]
 func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Get(input)
 }
@@ -70,10 +73,11 @@ func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceO
 // @Tags plugins/bamboo
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} []models.BambooTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/transformation_rules [GET]
+// @Router /plugins/bamboo/connections/{connectionId}/transformation_rules 
[GET]
 func GetTransformationRuleList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.List(input)
 }
diff --git a/backend/plugins/bamboo/impl/impl.go 
b/backend/plugins/bamboo/impl/impl.go
index 479cebd58..f7b9f4c94 100644
--- a/backend/plugins/bamboo/impl/impl.go
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -198,11 +198,11 @@ func (p Bamboo) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "PATCH":  api.PatchConnection,
                        "DELETE": api.DeleteConnection,
                },
-               "transformation_rules": {
+               "connections/:connectionId/transformation_rules": {
                        "POST": api.CreateTransformationRule,
                        "GET":  api.GetTransformationRuleList,
                },
-               "transformation_rules/:id": {
+               "connections/:connectionId/transformation_rules/:id": {
                        "PATCH": api.UpdateTransformationRule,
                        "GET":   api.GetTransformationRule,
                },
diff --git 
a/backend/plugins/bamboo/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
 
b/backend/plugins/bamboo/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
new file mode 100644
index 000000000..a72397df0
--- /dev/null
+++ 
b/backend/plugins/bamboo/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
@@ -0,0 +1,73 @@
+/*
+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 migrationscripts
+
+import (
+       "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/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/bamboo/models"
+)
+
+type addConnectionIdToTransformationRule struct{}
+
+type transformationRule20220322 struct {
+       ConnectionId uint64
+}
+
+func (transformationRule20220322) TableName() string {
+       return "_tool_bamboo_transformation_rules"
+}
+func (u *addConnectionIdToTransformationRule) Up(baseRes context.BasicRes) 
errors.Error {
+       err := migrationhelper.AutoMigrateTables(baseRes, 
&transformationRule20220322{})
+       if err != nil {
+               return err
+       }
+       var scopes []models.BambooProject
+       err = baseRes.GetDal().All(&scopes)
+       if err != nil {
+               return err
+       }
+       // get all rules that are not referenced.
+       idMap := make(map[uint64]uint64)
+       for _, scope := range scopes {
+               if idMap[scope.TransformationRuleId] == 0 {
+                       idMap[scope.TransformationRuleId] = scope.ConnectionId
+               }
+       }
+       // set connection_id for rules
+       for trId, cId := range idMap {
+               err = baseRes.GetDal().UpdateColumn(
+                       &models.BambooTransformationRule{}, "connection_id", 
cId,
+                       dal.Where("id = ?", trId))
+               if err != nil {
+                       return err
+               }
+       }
+       // delete all rules that are not referenced.
+       return baseRes.GetDal().Delete(&models.BambooTransformationRule{}, 
dal.Where("connection_id = ? OR connection_id = ?", nil, 0))
+}
+
+func (*addConnectionIdToTransformationRule) Version() uint64 {
+       return 20230322150357
+}
+
+func (*addConnectionIdToTransformationRule) Name() string {
+       return "add connection_id to _tool_bamboo_transformation_rules"
+}
diff --git a/backend/plugins/bamboo/models/migrationscripts/register.go 
b/backend/plugins/bamboo/models/migrationscripts/register.go
index ec054748c..d6c907212 100644
--- a/backend/plugins/bamboo/models/migrationscripts/register.go
+++ b/backend/plugins/bamboo/models/migrationscripts/register.go
@@ -25,5 +25,6 @@ import (
 func All() []plugin.MigrationScript {
        return []plugin.MigrationScript{
                new(addInitTables),
+               new(addConnectionIdToTransformationRule),
        }
 }
diff --git a/backend/plugins/bamboo/models/transformation_rule.go 
b/backend/plugins/bamboo/models/transformation_rule.go
index 2a6bca0cd..d4be4e9a5 100644
--- a/backend/plugins/bamboo/models/transformation_rule.go
+++ b/backend/plugins/bamboo/models/transformation_rule.go
@@ -24,7 +24,8 @@ import (
 
 type BambooTransformationRule struct {
        common.Model
-       Name string `gorm:"type:varchar(255);index:idx_name_gitlab,unique" 
validate:"required" mapstructure:"name" json:"name"`
+       ConnectionId uint64 `mapstructure:"connectionId" json:"connectionId"`
+       Name         string 
`gorm:"type:varchar(255);index:idx_name_gitlab,unique" validate:"required" 
mapstructure:"name" json:"name"`
        // should be {realRepoName: [bamboo_repoId]}
        RepoMap           datatypes.JSONMap
        DeploymentPattern string `mapstructure:"deploymentPattern,omitempty" 
json:"deploymentPattern" gorm:"type:varchar(255)"`
diff --git a/backend/plugins/bitbucket/api/transformation_rule.go 
b/backend/plugins/bitbucket/api/transformation_rule.go
index 9b37da739..922fbdb2f 100644
--- a/backend/plugins/bitbucket/api/transformation_rule.go
+++ b/backend/plugins/bitbucket/api/transformation_rule.go
@@ -27,11 +27,12 @@ import (
 // @Description create transformation rule for Bitbucket
 // @Tags plugins/bitbucket
 // @Accept application/json
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.BitbucketTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.BitbucketTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bitbucket/transformation_rules [POST]
+// @Router /plugins/bitbucket/connections/{connectionId}/transformation_rules 
[POST]
 func CreateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Create(input)
 }
@@ -42,11 +43,12 @@ func CreateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Tags plugins/bitbucket
 // @Accept application/json
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.BitbucketTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.BitbucketTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bitbucket/transformation_rules/{id} [PATCH]
+// @Router 
/plugins/bitbucket/connections/{connectionId}/transformation_rules/{id} [PATCH]
 func UpdateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Update(input)
 }
@@ -56,10 +58,11 @@ func UpdateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Description return one transformation rule
 // @Tags plugins/bitbucket
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.BitbucketTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bitbucket/transformation_rules/{id} [GET]
+// @Router 
/plugins/bitbucket/connections/{connectionId}/transformation_rules/{id} [GET]
 func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Get(input)
 }
@@ -68,12 +71,13 @@ func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceO
 // @Summary return all transformation rules
 // @Description return all transformation rules
 // @Tags plugins/bitbucket
+// @Param connectionId path int true "connectionId"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
 // @Success 200  {object} []models.BitbucketTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bitbucket/transformation_rules [GET]
+// @Router /plugins/bitbucket/connections/{connectionId}/transformation_rules 
[GET]
 func GetTransformationRuleList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.List(input)
 }
diff --git a/backend/plugins/bitbucket/impl/impl.go 
b/backend/plugins/bitbucket/impl/impl.go
index 45019017a..c1eb862b3 100644
--- a/backend/plugins/bitbucket/impl/impl.go
+++ b/backend/plugins/bitbucket/impl/impl.go
@@ -207,11 +207,11 @@ func (p Bitbucket) ApiResources() 
map[string]map[string]plugin.ApiResourceHandle
                        "GET": api.GetScopeList,
                        "PUT": api.PutScope,
                },
-               "transformation_rules": {
+               "connections/:connectionId/transformation_rules": {
                        "POST": api.CreateTransformationRule,
                        "GET":  api.GetTransformationRuleList,
                },
-               "transformation_rules/:id": {
+               "connections/:connectionId/transformation_rules/:id": {
                        "PATCH": api.UpdateTransformationRule,
                        "GET":   api.GetTransformationRule,
                },
diff --git 
a/backend/plugins/bitbucket/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
 
b/backend/plugins/bitbucket/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
new file mode 100644
index 000000000..c05d46a00
--- /dev/null
+++ 
b/backend/plugins/bitbucket/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
@@ -0,0 +1,74 @@
+/*
+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 migrationscripts
+
+import (
+       "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/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/bitbucket/models"
+)
+
+type addConnectionIdToTransformationRule struct{}
+
+type transformationRule20220322 struct {
+       ConnectionId uint64
+}
+
+func (transformationRule20220322) TableName() string {
+       return "_tool_bitbucket_transformation_rules"
+}
+
+func (u *addConnectionIdToTransformationRule) Up(baseRes context.BasicRes) 
errors.Error {
+       err := migrationhelper.AutoMigrateTables(baseRes, 
&transformationRule20220322{})
+       if err != nil {
+               return err
+       }
+       var scopes []models.BitbucketRepo
+       err = baseRes.GetDal().All(&scopes)
+       if err != nil {
+               return err
+       }
+       // get all rules that are not referenced.
+       idMap := make(map[uint64]uint64)
+       for _, scope := range scopes {
+               if scope.TransformationRuleId > 0 && 
idMap[scope.TransformationRuleId] == 0 {
+                       idMap[scope.TransformationRuleId] = scope.ConnectionId
+               }
+       }
+       // set connection_id for rules
+       for trId, cId := range idMap {
+               err = baseRes.GetDal().UpdateColumn(
+                       &models.BitbucketTransformationRule{}, "connection_id", 
cId,
+                       dal.Where("id = ?", trId))
+               if err != nil {
+                       return err
+               }
+       }
+       // delete all rules that are not referenced.
+       return baseRes.GetDal().Delete(&models.BitbucketTransformationRule{}, 
dal.Where("connection_id IS NULL OR connection_id = 0"))
+}
+
+func (*addConnectionIdToTransformationRule) Version() uint64 {
+       return 20230322150357
+}
+
+func (*addConnectionIdToTransformationRule) Name() string {
+       return "add connection_id to _tool_bitbucket_transformation_rules"
+}
diff --git a/backend/plugins/bitbucket/models/migrationscripts/register.go 
b/backend/plugins/bitbucket/models/migrationscripts/register.go
index bff36d850..27a5f1fa7 100644
--- a/backend/plugins/bitbucket/models/migrationscripts/register.go
+++ b/backend/plugins/bitbucket/models/migrationscripts/register.go
@@ -31,5 +31,6 @@ func All() []plugin.MigrationScript {
                new(addRepoIdAndCommitShaField20221014),
                new(addScope20230206),
                new(addPipelineStep20230215),
+               new(addConnectionIdToTransformationRule),
        }
 }
diff --git a/backend/plugins/bitbucket/models/transformation_rule.go 
b/backend/plugins/bitbucket/models/transformation_rule.go
index 26a622239..dd70788ba 100644
--- a/backend/plugins/bitbucket/models/transformation_rule.go
+++ b/backend/plugins/bitbucket/models/transformation_rule.go
@@ -24,6 +24,7 @@ import (
 
 type BitbucketTransformationRule struct {
        common.Model      `mapstructure:"-"`
+       ConnectionId      uint64            `mapstructure:"connectionId" 
json:"connectionId"`
        Name              string            `mapstructure:"name" json:"name" 
gorm:"type:varchar(255);index:idx_name_github,unique" validate:"required"`
        DeploymentPattern string            
`mapstructure:"deploymentPattern,omitempty" json:"deploymentPattern" 
gorm:"type:varchar(255)"`
        ProductionPattern string            
`mapstructure:"productionPattern,omitempty" json:"productionPattern" 
gorm:"type:varchar(255)"`
diff --git a/backend/plugins/github/api/transformation_rule.go 
b/backend/plugins/github/api/transformation_rule.go
index f064e675a..89a86bd12 100644
--- a/backend/plugins/github/api/transformation_rule.go
+++ b/backend/plugins/github/api/transformation_rule.go
@@ -27,11 +27,12 @@ import (
 // @Description create transformation rule for Github
 // @Tags plugins/github
 // @Accept application/json
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.GithubTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.GithubTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/github/transformation_rules [POST]
+// @Router /plugins/github/connections/{connectionId}/transformation_rules 
[POST]
 func CreateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Create(input)
 }
@@ -42,11 +43,12 @@ func CreateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Tags plugins/github
 // @Accept application/json
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.GithubTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.GithubTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/github/transformation_rules/{id} [PATCH]
+// @Router 
/plugins/github/connections/{connectionId}/transformation_rules/{id} [PATCH]
 func UpdateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Update(input)
 }
@@ -56,10 +58,11 @@ func UpdateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Description return one transformation rule
 // @Tags plugins/github
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.GithubTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/github/transformation_rules/{id} [GET]
+// @Router 
/plugins/github/connections/{connectionId}/transformation_rules/{id} [GET]
 func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Get(input)
 }
@@ -70,10 +73,11 @@ func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceO
 // @Tags plugins/github
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} []models.GithubTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/github/transformation_rules [GET]
+// @Router /plugins/github/connections/{connectionId}/transformation_rules 
[GET]
 func GetTransformationRuleList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.List(input)
 }
diff --git a/backend/plugins/github/impl/impl.go 
b/backend/plugins/github/impl/impl.go
index 71dd2dcea..d438632f2 100644
--- a/backend/plugins/github/impl/impl.go
+++ b/backend/plugins/github/impl/impl.go
@@ -212,11 +212,11 @@ func (p Github) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET": api.GetScopeList,
                        "PUT": api.PutScope,
                },
-               "transformation_rules": {
+               "connections/:connectionId/transformation_rules": {
                        "POST": api.CreateTransformationRule,
                        "GET":  api.GetTransformationRuleList,
                },
-               "transformation_rules/:id": {
+               "connections/:connectionId/transformation_rules/:id": {
                        "PATCH": api.UpdateTransformationRule,
                        "GET":   api.GetTransformationRule,
                },
diff --git 
a/backend/plugins/github/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
 
b/backend/plugins/github/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
new file mode 100644
index 000000000..61db54ce7
--- /dev/null
+++ 
b/backend/plugins/github/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
@@ -0,0 +1,74 @@
+/*
+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 migrationscripts
+
+import (
+       "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/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/github/models"
+)
+
+type addConnectionIdToTransformationRule struct{}
+
+type transformationRule20220322 struct {
+       ConnectionId uint64
+}
+
+func (transformationRule20220322) TableName() string {
+       return "_tool_github_transformation_rules"
+}
+
+func (u *addConnectionIdToTransformationRule) Up(baseRes context.BasicRes) 
errors.Error {
+       err := migrationhelper.AutoMigrateTables(baseRes, 
&transformationRule20220322{})
+       if err != nil {
+               return err
+       }
+       var scopes []models.GithubRepo
+       err = baseRes.GetDal().All(&scopes)
+       if err != nil {
+               return err
+       }
+       // get all rules that are not referenced.
+       idMap := make(map[uint64]uint64)
+       for _, scope := range scopes {
+               if scope.TransformationRuleId > 0 && 
idMap[scope.TransformationRuleId] == 0 {
+                       idMap[scope.TransformationRuleId] = scope.ConnectionId
+               }
+       }
+       // set connection_id for rules
+       for trId, cId := range idMap {
+               err = baseRes.GetDal().UpdateColumn(
+                       &models.GithubTransformationRule{}, "connection_id", 
cId,
+                       dal.Where("id = ?", trId))
+               if err != nil {
+                       return err
+               }
+       }
+       // delete all rules that are not referenced.
+       return baseRes.GetDal().Delete(&models.GithubTransformationRule{}, 
dal.Where("connection_id IS NULL OR connection_id = 0"))
+}
+
+func (*addConnectionIdToTransformationRule) Version() uint64 {
+       return 20230322150357
+}
+
+func (*addConnectionIdToTransformationRule) Name() string {
+       return "add connection_id to _tool_github_transformation_rules"
+}
diff --git a/backend/plugins/github/models/migrationscripts/register.go 
b/backend/plugins/github/models/migrationscripts/register.go
index 9097ca55a..85a1859e7 100644
--- a/backend/plugins/github/models/migrationscripts/register.go
+++ b/backend/plugins/github/models/migrationscripts/register.go
@@ -34,5 +34,6 @@ func All() []plugin.MigrationScript {
                new(addTransformationRule20221124),
                new(concatOwnerAndName),
                new(addStdTypeToIssue221230),
+               new(addConnectionIdToTransformationRule),
        }
 }
diff --git a/backend/plugins/github/models/transformation_rule.go 
b/backend/plugins/github/models/transformation_rule.go
index 0bcc6fb22..d7ecb35df 100644
--- a/backend/plugins/github/models/transformation_rule.go
+++ b/backend/plugins/github/models/transformation_rule.go
@@ -24,6 +24,7 @@ import (
 
 type GithubTransformationRule struct {
        common.Model         `mapstructure:"-"`
+       ConnectionId         uint64            `mapstructure:"connectionId" 
json:"connectionId"`
        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)"`
diff --git a/backend/plugins/gitlab/api/transformation_rule.go 
b/backend/plugins/gitlab/api/transformation_rule.go
index 10cfec795..73de6c296 100644
--- a/backend/plugins/gitlab/api/transformation_rule.go
+++ b/backend/plugins/gitlab/api/transformation_rule.go
@@ -27,11 +27,12 @@ import (
 // @Description create transformation rule for Gitlab
 // @Tags plugins/gitlab
 // @Accept application/json
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.GitlabTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.GitlabTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/gitlab/transformation_rules [POST]
+// @Router /plugins/gitlab/connections/{connectionId}/transformation_rules 
[POST]
 func CreateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Create(input)
 }
@@ -42,11 +43,12 @@ func CreateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Tags plugins/gitlab
 // @Accept application/json
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.GitlabTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.GitlabTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/gitlab/transformation_rules/{id} [PATCH]
+// @Router 
/plugins/gitlab/connections/{connectionId}/transformation_rules/{id} [PATCH]
 func UpdateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Update(input)
 }
@@ -56,10 +58,11 @@ func UpdateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Description return one transformation rule
 // @Tags plugins/gitlab
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.GitlabTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/gitlab/transformation_rules/{id} [GET]
+// @Router 
/plugins/gitlab/connections/{connectionId}/transformation_rules/{id} [GET]
 func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Get(input)
 }
@@ -68,12 +71,13 @@ func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceO
 // @Summary return all transformation rules
 // @Description return all transformation rules
 // @Tags plugins/gitlab
+// @Param connectionId path int true "connectionId"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
 // @Success 200  {object} []models.GitlabTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/gitlab/transformation_rules [GET]
+// @Router /plugins/gitlab/connections/{connectionId}/transformation_rules 
[GET]
 func GetTransformationRuleList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.List(input)
 }
diff --git a/backend/plugins/gitlab/impl/impl.go 
b/backend/plugins/gitlab/impl/impl.go
index aa1f38d67..561538ae5 100644
--- a/backend/plugins/gitlab/impl/impl.go
+++ b/backend/plugins/gitlab/impl/impl.go
@@ -261,11 +261,11 @@ func (p Gitlab) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET": api.GetScopeList,
                        "PUT": api.PutScope,
                },
-               "transformation_rules": {
+               "connections/:connectionId/transformation_rules": {
                        "POST": api.CreateTransformationRule,
                        "GET":  api.GetTransformationRuleList,
                },
-               "transformation_rules/:id": {
+               "connections/:connectionId/transformation_rules/:id": {
                        "PATCH": api.UpdateTransformationRule,
                        "GET":   api.GetTransformationRule,
                },
diff --git 
a/backend/plugins/gitlab/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
 
b/backend/plugins/gitlab/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
new file mode 100644
index 000000000..4f710c71a
--- /dev/null
+++ 
b/backend/plugins/gitlab/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
@@ -0,0 +1,74 @@
+/*
+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 migrationscripts
+
+import (
+       "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/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/gitlab/models"
+)
+
+type addConnectionIdToTransformationRule struct{}
+
+type transformationRule20220322 struct {
+       ConnectionId uint64
+}
+
+func (transformationRule20220322) TableName() string {
+       return "_tool_gitlab_transformation_rules"
+}
+
+func (u *addConnectionIdToTransformationRule) Up(baseRes context.BasicRes) 
errors.Error {
+       err := migrationhelper.AutoMigrateTables(baseRes, 
&transformationRule20220322{})
+       if err != nil {
+               return err
+       }
+       var scopes []models.GitlabProject
+       err = baseRes.GetDal().All(&scopes)
+       if err != nil {
+               return err
+       }
+       // get all rules that are not referenced.
+       idMap := make(map[uint64]uint64)
+       for _, scope := range scopes {
+               if scope.TransformationRuleId > 0 && 
idMap[scope.TransformationRuleId] == 0 {
+                       idMap[scope.TransformationRuleId] = scope.ConnectionId
+               }
+       }
+       // set connection_id for rules
+       for trId, cId := range idMap {
+               err = baseRes.GetDal().UpdateColumn(
+                       &models.GitlabTransformationRule{}, "connection_id", 
cId,
+                       dal.Where("id = ?", trId))
+               if err != nil {
+                       return err
+               }
+       }
+       // delete all rules that are not referenced.
+       return baseRes.GetDal().Delete(&models.GitlabTransformationRule{}, 
dal.Where("connection_id IS NULL OR connection_id = 0"))
+}
+
+func (*addConnectionIdToTransformationRule) Version() uint64 {
+       return 20230322150357
+}
+
+func (*addConnectionIdToTransformationRule) Name() string {
+       return "add connection_id to _tool_gitlab_transformation_rules"
+}
diff --git a/backend/plugins/gitlab/models/migrationscripts/register.go 
b/backend/plugins/gitlab/models/migrationscripts/register.go
index 562e48032..9612cf3f7 100644
--- a/backend/plugins/gitlab/models/migrationscripts/register.go
+++ b/backend/plugins/gitlab/models/migrationscripts/register.go
@@ -32,5 +32,6 @@ func All() []plugin.MigrationScript {
                new(addTransformationRule20221125),
                new(addStdTypeToIssue221230),
                new(addIsDetailRequired20230210),
+               new(addConnectionIdToTransformationRule),
        }
 }
diff --git a/backend/plugins/gitlab/models/transformation_rule.go 
b/backend/plugins/gitlab/models/transformation_rule.go
index 3d65b2aa0..255f30860 100644
--- a/backend/plugins/gitlab/models/transformation_rule.go
+++ b/backend/plugins/gitlab/models/transformation_rule.go
@@ -24,6 +24,7 @@ import (
 
 type GitlabTransformationRule struct {
        common.Model
+       ConnectionId         uint64            `mapstructure:"connectionId" 
json:"connectionId"`
        Name                 string            
`gorm:"type:varchar(255);index:idx_name_gitlab,unique" validate:"required" 
mapstructure:"name" json:"name"`
        PrType               string            `mapstructure:"prType" 
json:"prType"`
        PrComponent          string            `mapstructure:"prComponent" 
json:"prComponent"`
diff --git a/backend/plugins/jenkins/api/transformation_rule.go 
b/backend/plugins/jenkins/api/transformation_rule.go
index 9cd27682a..31377baa5 100644
--- a/backend/plugins/jenkins/api/transformation_rule.go
+++ b/backend/plugins/jenkins/api/transformation_rule.go
@@ -27,11 +27,12 @@ import (
 // @Description create transformation rule for Jenkins
 // @Tags plugins/jenkins
 // @Accept application/json
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body models.JenkinsTransformationRule true 
"transformation rule"
 // @Success 200  {object} models.JenkinsTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jenkins/transformation_rules [POST]
+// @Router /plugins/jenkins/connections/{connectionId}/transformation_rules 
[POST]
 func CreateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Create(input)
 }
@@ -43,10 +44,11 @@ func CreateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Accept application/json
 // @Param id path int true "id"
 // @Param transformationRule body models.JenkinsTransformationRule true 
"transformation rule"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.JenkinsTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jenkins/transformation_rules/{id} [PATCH]
+// @Router 
/plugins/jenkins/connections/{connectionId}/transformation_rules/{id} [PATCH]
 func UpdateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Update(input)
 }
@@ -56,10 +58,11 @@ func UpdateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Description return one transformation rule
 // @Tags plugins/jenkins
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} models.JenkinsTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jenkins/transformation_rules/{id} [GET]
+// @Router 
/plugins/jenkins/connections/{connectionId}/transformation_rules/{id} [GET]
 func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Update(input)
 }
@@ -70,10 +73,11 @@ func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceO
 // @Tags plugins/jenkins
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} []models.JenkinsTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jenkins/transformation_rules [GET]
+// @Router /plugins/jenkins/connections/{connectionId}/transformation_rules 
[GET]
 func GetTransformationRuleList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.List(input)
 }
diff --git a/backend/plugins/jenkins/impl/impl.go 
b/backend/plugins/jenkins/impl/impl.go
index 04c2a3742..fa4986cf8 100644
--- a/backend/plugins/jenkins/impl/impl.go
+++ b/backend/plugins/jenkins/impl/impl.go
@@ -181,11 +181,11 @@ func (p Jenkins) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler
                        "GET": api.GetScopeList,
                        "PUT": api.PutScope,
                },
-               "transformation_rules": {
+               "connections/:connectionId/transformation_rules": {
                        "POST": api.CreateTransformationRule,
                        "GET":  api.GetTransformationRuleList,
                },
-               "transformation_rules/:id": {
+               "connections/:connectionId/transformation_rules/:id": {
                        "PATCH": api.UpdateTransformationRule,
                        "GET":   api.GetTransformationRule,
                },
diff --git 
a/backend/plugins/jenkins/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
 
b/backend/plugins/jenkins/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
new file mode 100644
index 000000000..38236b7f4
--- /dev/null
+++ 
b/backend/plugins/jenkins/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
@@ -0,0 +1,74 @@
+/*
+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 migrationscripts
+
+import (
+       "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/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/jenkins/models"
+)
+
+type addConnectionIdToTransformationRule struct{}
+
+type transformationRule20220322 struct {
+       ConnectionId uint64
+}
+
+func (transformationRule20220322) TableName() string {
+       return "_tool_jenkins_transformation_rules"
+}
+
+func (u *addConnectionIdToTransformationRule) Up(baseRes context.BasicRes) 
errors.Error {
+       err := migrationhelper.AutoMigrateTables(baseRes, 
&transformationRule20220322{})
+       if err != nil {
+               return err
+       }
+       var scopes []models.JenkinsJob
+       err = baseRes.GetDal().All(&scopes)
+       if err != nil {
+               return err
+       }
+       // get all rules that are not referenced.
+       idMap := make(map[uint64]uint64)
+       for _, scope := range scopes {
+               if scope.TransformationRuleId > 0 && 
idMap[scope.TransformationRuleId] == 0 {
+                       idMap[scope.TransformationRuleId] = scope.ConnectionId
+               }
+       }
+       // set connection_id for rules
+       for trId, cId := range idMap {
+               err = baseRes.GetDal().UpdateColumn(
+                       &models.JenkinsTransformationRule{}, "connection_id", 
cId,
+                       dal.Where("id = ?", trId))
+               if err != nil {
+                       return err
+               }
+       }
+       // delete all rules that are not referenced.
+       return baseRes.GetDal().Delete(&models.JenkinsTransformationRule{}, 
dal.Where("connection_id IS NULL OR connection_id = 0"))
+}
+
+func (*addConnectionIdToTransformationRule) Version() uint64 {
+       return 20230322150357
+}
+
+func (*addConnectionIdToTransformationRule) Name() string {
+       return "add connection_id to _tool_jenkins_transformation_rules"
+}
diff --git a/backend/plugins/jenkins/models/migrationscripts/register.go 
b/backend/plugins/jenkins/models/migrationscripts/register.go
index 8e451c273..bc0d54f57 100644
--- a/backend/plugins/jenkins/models/migrationscripts/register.go
+++ b/backend/plugins/jenkins/models/migrationscripts/register.go
@@ -32,5 +32,6 @@ func All() []plugin.MigrationScript {
                new(changeIndexOfJobPath),
                new(addTransformationRule20221128),
                new(addFullNameForBuilds),
+               new(addConnectionIdToTransformationRule),
        }
 }
diff --git a/backend/plugins/jenkins/models/transformation_rule.go 
b/backend/plugins/jenkins/models/transformation_rule.go
index 70b44b2f7..2845f0a04 100644
--- a/backend/plugins/jenkins/models/transformation_rule.go
+++ b/backend/plugins/jenkins/models/transformation_rule.go
@@ -23,6 +23,7 @@ import (
 
 type JenkinsTransformationRule struct {
        common.Model      `mapstructure:"-"`
+       ConnectionId      uint64 `mapstructure:"connectionId" 
json:"connectionId"`
        Name              string 
`gorm:"type:varchar(255);index:idx_name_jenkins,unique" validate:"required" 
mapstructure:"name" json:"name"`
        DeploymentPattern string `gorm:"type:varchar(255)" 
mapstructure:"deploymentPattern,omitempty" json:"deploymentPattern"`
        ProductionPattern string `gorm:"type:varchar(255)" 
mapstructure:"productionPattern,omitempty" json:"productionPattern"`
diff --git a/backend/plugins/jira/api/transformation_rule.go 
b/backend/plugins/jira/api/transformation_rule.go
index e8b44e417..f3fffe97c 100644
--- a/backend/plugins/jira/api/transformation_rule.go
+++ b/backend/plugins/jira/api/transformation_rule.go
@@ -18,6 +18,10 @@ limitations under the License.
 package api
 
 import (
+       "net/http"
+       "strconv"
+
+       "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
@@ -31,11 +35,12 @@ import (
 // @Description create transformation rule for Jira
 // @Tags plugins/jira
 // @Accept application/json
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body tasks.JiraTransformationRule true 
"transformation rule"
 // @Success 200  {object} tasks.JiraTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jira/transformation_rules [POST]
+// @Router /plugins/jira/connections/{connectionId}/transformation_rules [POST]
 func CreateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        rule, err := makeDbTransformationRuleFromInput(input)
        if err != nil {
@@ -56,40 +61,65 @@ func CreateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 // @Tags plugins/jira
 // @Accept application/json
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Param transformationRule body tasks.JiraTransformationRule true 
"transformation rule"
 // @Success 200  {object} tasks.JiraTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jira/transformation_rules/{id} [PATCH]
+// @Router /plugins/jira/connections/{connectionId}/transformation_rules/{id} 
[PATCH]
 func UpdateTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       rule, err := makeDbTransformationRuleFromInput(input)
+       connectionId, e := strconv.ParseUint(input.Params["connectionId"], 10, 
64)
+       if e != nil || connectionId == 0 {
+               return nil, errors.Default.Wrap(e, "the connection ID should be 
an non-zero integer")
+       }
+       transformationRuleId, e := strconv.ParseUint(input.Params["id"], 10, 64)
+       if e != nil {
+               return nil, errors.Default.Wrap(e, "the transformation rule ID 
should be an integer")
+       }
+       var req tasks.JiraTransformationRule
+       err := api.Decode(input.Body, &req, vld)
        if err != nil {
-               return nil, errors.BadInput.Wrap(err, "error in 
makeJiraTransformationRule")
+               return nil, err
        }
-       newRule := map[string]interface{}{}
-       err = errors.Convert(mapstructure.Decode(rule, &newRule))
+       var oldDB models.JiraTransformationRule
+       err = basicRes.GetDal().First(&oldDB, dal.Where("id = ?", 
transformationRuleId))
        if err != nil {
-               return nil, errors.BadInput.Wrap(err, "error in 
makeJiraTransformationRule")
+               return nil, errors.Default.Wrap(err, "error on getting 
TransformationRule")
        }
-       input.Body = newRule
-       output, err := trHelper.Update(input)
+       oldTr, err := tasks.MakeTransformationRules(oldDB)
+       if err != nil {
+               return nil, err
+       }
+       err = api.DecodeMapStruct(input.Body, oldTr)
+       if err != nil {
+               return nil, err
+       }
+
+       newDB, err := oldTr.ToDb()
        if err != nil {
                return nil, err
        }
-       tr := output.Body.(models.JiraTransformationRule)
-       err = tr.VerifyRegexp()
+       newDB.ID = transformationRuleId
+       newDB.ConnectionId = connectionId
+       newDB.CreatedAt = oldDB.CreatedAt
+       err = basicRes.GetDal().Update(newDB)
        if err != nil {
-               return nil, errors.Default.Wrap(err, "error verify the regexps 
of transformationRule")
+               return nil, err
        }
-       return output, err
+       return &plugin.ApiResourceOutput{Body: newDB, Status: http.StatusOK}, 
err
 }
 
 func makeDbTransformationRuleFromInput(input *plugin.ApiResourceInput) 
(*models.JiraTransformationRule, errors.Error) {
+       connectionId, e := strconv.ParseUint(input.Params["connectionId"], 10, 
64)
+       if e != nil || connectionId == 0 {
+               return nil, errors.Default.Wrap(e, "the connection ID should be 
an non-zero integer")
+       }
        var req tasks.JiraTransformationRule
        err := api.Decode(input.Body, &req, vld)
        if err != nil {
                return nil, err
        }
+       req.ConnectionId = connectionId
        return req.ToDb()
 }
 
@@ -98,10 +128,11 @@ func makeDbTransformationRuleFromInput(input 
*plugin.ApiResourceInput) (*models.
 // @Description return one transformation rule
 // @Tags plugins/jira
 // @Param id path int true "id"
+// @Param connectionId path int true "connectionId"
 // @Success 200  {object} tasks.JiraTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jira/transformation_rules/{id} [GET]
+// @Router /plugins/jira/connections/{connectionId}/transformation_rules/{id} 
[GET]
 func GetTransformationRule(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.Get(input)
 }
@@ -110,12 +141,13 @@ func GetTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResourceO
 // @Summary return all transformation rules
 // @Description return all transformation rules
 // @Tags plugins/jira
+// @Param connectionId path int true "connectionId"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
 // @Success 200  {object} []tasks.JiraTransformationRule
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/jira/transformation_rules [GET]
+// @Router /plugins/jira/connections/{connectionId}/transformation_rules [GET]
 func GetTransformationRuleList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return trHelper.List(input)
 }
diff --git a/backend/plugins/jira/impl/impl.go 
b/backend/plugins/jira/impl/impl.go
index 3663a672d..cb8c21515 100644
--- a/backend/plugins/jira/impl/impl.go
+++ b/backend/plugins/jira/impl/impl.go
@@ -291,11 +291,11 @@ func (p Jira) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET": api.GetScopeList,
                        "PUT": api.PutScope,
                },
-               "transformation_rules": {
+               "connections/:connectionId/transformation_rules": {
                        "POST": api.CreateTransformationRule,
                        "GET":  api.GetTransformationRuleList,
                },
-               "transformation_rules/:id": {
+               "connections/:connectionId/transformation_rules/:id": {
                        "PATCH": api.UpdateTransformationRule,
                        "GET":   api.GetTransformationRule,
                },
diff --git 
a/backend/plugins/jira/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
 
b/backend/plugins/jira/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
new file mode 100644
index 000000000..7a6af67a5
--- /dev/null
+++ 
b/backend/plugins/jira/models/migrationscripts/20230322_add_connection_id_to_transformation_rules.go
@@ -0,0 +1,74 @@
+/*
+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 migrationscripts
+
+import (
+       "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/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/jira/models"
+)
+
+type addConnectionIdToTransformationRule struct{}
+
+type transformationRule20220322 struct {
+       ConnectionId uint64
+}
+
+func (transformationRule20220322) TableName() string {
+       return "_tool_jira_transformation_rules"
+}
+
+func (u *addConnectionIdToTransformationRule) Up(baseRes context.BasicRes) 
errors.Error {
+       err := migrationhelper.AutoMigrateTables(baseRes, 
&transformationRule20220322{})
+       if err != nil {
+               return err
+       }
+       var scopes []models.JiraBoard
+       err = baseRes.GetDal().All(&scopes)
+       if err != nil {
+               return err
+       }
+       // get all rules that are not referenced.
+       idMap := make(map[uint64]uint64)
+       for _, scope := range scopes {
+               if scope.TransformationRuleId > 0 && 
idMap[scope.TransformationRuleId] == 0 {
+                       idMap[scope.TransformationRuleId] = scope.ConnectionId
+               }
+       }
+       // set connection_id for rules
+       for trId, cId := range idMap {
+               err = baseRes.GetDal().UpdateColumn(
+                       &models.JiraTransformationRule{}, "connection_id", cId,
+                       dal.Where("id = ?", trId))
+               if err != nil {
+                       return err
+               }
+       }
+       // delete all rules that are not referenced.
+       return baseRes.GetDal().Delete(&models.JiraTransformationRule{}, 
dal.Where("connection_id IS NULL OR connection_id = 0"))
+}
+
+func (*addConnectionIdToTransformationRule) Version() uint64 {
+       return 20230322150357
+}
+
+func (*addConnectionIdToTransformationRule) Name() string {
+       return "add connection_id to _tool_jira_transformation_rules"
+}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go 
b/backend/plugins/jira/models/migrationscripts/register.go
index 8a69859f5..996656967 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++ b/backend/plugins/jira/models/migrationscripts/register.go
@@ -33,5 +33,6 @@ func All() []plugin.MigrationScript {
                new(removeIssueStdStoryPoint),
                new(addCommitRepoPattern),
                new(expandRemotelinkUrl),
+               new(addConnectionIdToTransformationRule),
        }
 }
diff --git a/backend/plugins/jira/models/transformation_rules.go 
b/backend/plugins/jira/models/transformation_rules.go
index bb2b7e876..58d104555 100644
--- a/backend/plugins/jira/models/transformation_rules.go
+++ b/backend/plugins/jira/models/transformation_rules.go
@@ -27,6 +27,7 @@ import (
 
 type JiraTransformationRule struct {
        common.Model               `mapstructure:"-"`
+       ConnectionId               uint64          `mapstructure:"connectionId" 
json:"connectionId"`
        Name                       string          `mapstructure:"name" 
json:"name" gorm:"type:varchar(255);index:idx_name_jira,unique" 
validate:"required"`
        EpicKeyField               string          
`mapstructure:"epicKeyField,omitempty" json:"epicKeyField" 
gorm:"type:varchar(255)"`
        StoryPointField            string          
`mapstructure:"storyPointField,omitempty" json:"storyPointField" 
gorm:"type:varchar(255)"`
diff --git a/backend/plugins/jira/tasks/task_data.go 
b/backend/plugins/jira/tasks/task_data.go
index 3a5e3933f..97b0174af 100644
--- a/backend/plugins/jira/tasks/task_data.go
+++ b/backend/plugins/jira/tasks/task_data.go
@@ -41,6 +41,7 @@ type TypeMapping struct {
 type TypeMappings map[string]TypeMapping
 
 type JiraTransformationRule struct {
+       ConnectionId               uint64       `mapstructure:"connectionId" 
json:"connectionId"`
        Name                       string       `gorm:"type:varchar(255)" 
validate:"required"`
        EpicKeyField               string       `json:"epicKeyField"`
        StoryPointField            string       `json:"storyPointField"`
@@ -59,6 +60,7 @@ func (r *JiraTransformationRule) ToDb() 
(*models.JiraTransformationRule, errors.
                return nil, errors.Default.Wrap(err, "error marshaling 
RemotelinkRepoPattern")
        }
        rule := &models.JiraTransformationRule{
+               ConnectionId:               r.ConnectionId,
                Name:                       r.Name,
                EpicKeyField:               r.EpicKeyField,
                StoryPointField:            r.StoryPointField,
@@ -89,6 +91,7 @@ func MakeTransformationRules(rule 
models.JiraTransformationRule) (*JiraTransform
                }
        }
        result := &JiraTransformationRule{
+               ConnectionId:               rule.ConnectionId,
                Name:                       rule.Name,
                EpicKeyField:               rule.EpicKeyField,
                StoryPointField:            rule.StoryPointField,

Reply via email to