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

klesh 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 b6c9e6cf6 [feat-5277]: Delete Scopes support for all plugins (#5290)
b6c9e6cf6 is described below

commit b6c9e6cf6aa4a0e07443c6f98eea2e7adf528e41
Author: Keon Amini <[email protected]>
AuthorDate: Fri May 26 02:21:45 2023 -0500

    [feat-5277]: Delete Scopes support for all plugins (#5290)
    
    * refactor: delete scopes won't return blueprints anymore because that's 
not needed
    
    * refactor: Plugins adapted to use the new ScopeHelper + DeleteScopes 
implemented + Swagger docs updated
---
 .../pluginhelper/api/scope_generic_helper.go       | 37 ++++++++++-----------
 backend/helpers/pluginhelper/api/scope_helper.go   | 35 +++-----------------
 .../helpers/pluginhelper/api/scope_helper_test.go  |  2 +-
 backend/plugins/bamboo/api/init.go                 |  8 +++++
 backend/plugins/bamboo/api/scope.go                | 20 ++++++++++--
 backend/plugins/bamboo/impl/impl.go                |  5 +--
 backend/plugins/bitbucket/api/init.go              |  8 +++++
 backend/plugins/bitbucket/api/scope.go             | 20 ++++++++++--
 backend/plugins/bitbucket/impl/impl.go             |  5 +--
 backend/plugins/github/api/init.go                 | 27 +++++++++++++++
 backend/plugins/github/api/scope.go                | 20 ++++++++++--
 backend/plugins/github/impl/impl.go                |  5 +--
 backend/plugins/gitlab/api/init.go                 |  9 ++++-
 backend/plugins/gitlab/api/scope.go                | 20 ++++++++++--
 backend/plugins/gitlab/impl/impl.go                |  5 +--
 backend/plugins/jenkins/api/init.go                |  8 +++++
 backend/plugins/jenkins/api/scope.go               | 20 ++++++++++--
 backend/plugins/jenkins/impl/impl.go               |  5 +--
 backend/plugins/jira/api/init.go                   |  9 ++++-
 backend/plugins/jira/api/scope.go                  | 20 ++++++++++--
 backend/plugins/jira/impl/impl.go                  |  5 +--
 backend/plugins/pagerduty/api/init.go              |  2 +-
 backend/plugins/pagerduty/api/scope.go             |  6 ++--
 backend/plugins/sonarqube/api/init.go              | 10 +++++-
 backend/plugins/sonarqube/api/scope.go             | 20 ++++++++++--
 backend/plugins/sonarqube/impl/impl.go             |  5 +--
 backend/plugins/tapd/api/init.go                   |  8 +++++
 backend/plugins/tapd/api/scope.go                  | 20 ++++++++++--
 backend/plugins/tapd/impl/impl.go                  |  5 +--
 backend/plugins/trello/api/scope.go                | 16 +++++++++
 backend/plugins/trello/impl/impl.go                |  5 +--
 backend/plugins/zentao/api/init.go                 | 16 +++++++++
 backend/plugins/zentao/api/scope.go                | 38 +++++++++++++++++++---
 backend/plugins/zentao/impl/impl.go                | 10 +++---
 backend/server/services/remote/plugin/scope_api.go |  4 +--
 35 files changed, 356 insertions(+), 102 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/scope_generic_helper.go 
b/backend/helpers/pluginhelper/api/scope_generic_helper.go
index 20b8be0eb..ede430882 100644
--- a/backend/helpers/pluginhelper/api/scope_generic_helper.go
+++ b/backend/helpers/pluginhelper/api/scope_generic_helper.go
@@ -270,17 +270,17 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
GetScope(input *plugin.ApiResou
        return scopeRes, nil
 }
 
-func (c *GenericScopeApiHelper[Conn, Scope, Tr]) DeleteScope(input 
*plugin.ApiResourceInput) ([]*models.Blueprint, errors.Error) {
+func (c *GenericScopeApiHelper[Conn, Scope, Tr]) DeleteScope(input 
*plugin.ApiResourceInput) errors.Error {
        params := c.extractFromDeleteReqParam(input)
        if params == nil || params.connectionId == 0 {
-               return nil, errors.BadInput.New("invalid path params: 
\"connectionId\" not set")
+               return errors.BadInput.New("invalid path params: 
\"connectionId\" not set")
        }
        if len(params.scopeId) == 0 || params.scopeId == "0" {
-               return nil, errors.BadInput.New("invalid path params: 
\"scopeId\" not set/invalid")
+               return errors.BadInput.New("invalid path params: \"scopeId\" 
not set/invalid")
        }
        err := c.dbHelper.VerifyConnection(params.connectionId)
        if err != nil {
-               return nil, errors.Default.Wrap(err, fmt.Sprintf("error 
verifying connection for connection ID %d", params.connectionId))
+               return errors.Default.Wrap(err, fmt.Sprintf("error verifying 
connection for connection ID %d", params.connectionId))
        }
        // delete all the plugin records referencing this scope
        if c.reflectionParams.RawScopeParamName != "" {
@@ -288,32 +288,31 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
DeleteScope(input *plugin.ApiRe
                if c.opts.GetScopeParamValue != nil {
                        scopeParamValue, err = c.opts.GetScopeParamValue(c.db, 
params.scopeId)
                        if err != nil {
-                               return nil, errors.Default.Wrap(err, 
fmt.Sprintf("error extracting scope parameter name for scope %s", 
params.scopeId))
+                               return errors.Default.Wrap(err, 
fmt.Sprintf("error extracting scope parameter name for scope %s", 
params.scopeId))
                        }
                }
                // find all tables for this plugin
                tables, err := getAffectedTables(params.plugin)
                if err != nil {
-                       return nil, errors.Default.Wrap(err, fmt.Sprintf("error 
getting database tables managed by plugin %s", params.plugin))
+                       return errors.Default.Wrap(err, fmt.Sprintf("error 
getting database tables managed by plugin %s", params.plugin))
                }
                err = c.transactionalDelete(tables, scopeParamValue)
                if err != nil {
-                       return nil, errors.Default.Wrap(err, fmt.Sprintf("error 
deleting data bound to scope %s for plugin %s", params.scopeId, params.plugin))
+                       return errors.Default.Wrap(err, fmt.Sprintf("error 
deleting data bound to scope %s for plugin %s", params.scopeId, params.plugin))
                }
        }
-       var impactedBlueprints []*models.Blueprint
        if !params.deleteDataOnly {
                // Delete the scope itself
                err = c.dbHelper.DeleteScope(params.connectionId, 
params.scopeId)
                if err != nil {
-                       return nil, errors.Default.Wrap(err, fmt.Sprintf("error 
deleting scope %s", params.scopeId))
+                       return errors.Default.Wrap(err, fmt.Sprintf("error 
deleting scope %s", params.scopeId))
                }
-               impactedBlueprints, err = 
c.updateBlueprints(params.connectionId, params.scopeId)
+               err = c.updateBlueprints(params.connectionId, params.scopeId)
                if err != nil {
-                       return nil, err
+                       return err
                }
        }
-       return impactedBlueprints, nil
+       return nil
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) addTransformationName(scopes 
...*Scope) ([]*ScopeRes[Scope], errors.Error) {
@@ -507,13 +506,12 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
validatePrimaryKeys(scopes []*S
        return nil
 }
 
-func (c *GenericScopeApiHelper[Conn, Scope, Tr]) updateBlueprints(connectionId 
uint64, scopeId string) ([]*models.Blueprint, errors.Error) {
+func (c *GenericScopeApiHelper[Conn, Scope, Tr]) updateBlueprints(connectionId 
uint64, scopeId string) errors.Error {
        blueprintsMap, err := c.bpManager.GetBlueprintsByScopes(connectionId, 
scopeId)
        if err != nil {
-               return nil, errors.Default.Wrap(err, fmt.Sprintf("error 
retrieving scope with scope ID %s", scopeId))
+               return errors.Default.Wrap(err, fmt.Sprintf("error retrieving 
scope with scope ID %s", scopeId))
        }
        blueprints := blueprintsMap[scopeId]
-       var impactedBlueprints []*models.Blueprint
        // update the blueprints (remove scope reference from them)
        for _, blueprint := range blueprints {
                settings, _ := blueprint.UnmarshalSettings()
@@ -531,21 +529,20 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
updateBlueprints(connectionId u
                        return nil
                })
                if err != nil {
-                       return nil, errors.Default.Wrap(err, fmt.Sprintf("error 
removing scope %s from blueprint %d", scopeId, blueprint.ID))
+                       return errors.Default.Wrap(err, fmt.Sprintf("error 
removing scope %s from blueprint %d", scopeId, blueprint.ID))
                }
                if changed {
                        err = blueprint.UpdateSettings(&settings)
                        if err != nil {
-                               return nil, errors.Default.Wrap(err, 
fmt.Sprintf("error writing new settings into blueprint %s", blueprint.Name))
+                               return errors.Default.Wrap(err, 
fmt.Sprintf("error writing new settings into blueprint %s", blueprint.Name))
                        }
                        err = c.bpManager.SaveDbBlueprint(blueprint)
                        if err != nil {
-                               return nil, errors.Default.Wrap(err, 
fmt.Sprintf("error saving the updated blueprint %s", blueprint.Name))
+                               return errors.Default.Wrap(err, 
fmt.Sprintf("error saving the updated blueprint %s", blueprint.Name))
                        }
-                       impactedBlueprints = append(impactedBlueprints, 
blueprint)
                }
        }
-       return impactedBlueprints, nil
+       return nil
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) transactionalDelete(tables 
[]string, scopeId string) errors.Error {
diff --git a/backend/helpers/pluginhelper/api/scope_helper.go 
b/backend/helpers/pluginhelper/api/scope_helper.go
index 51439f2d3..c95284dda 100644
--- a/backend/helpers/pluginhelper/api/scope_helper.go
+++ b/backend/helpers/pluginhelper/api/scope_helper.go
@@ -41,25 +41,8 @@ type (
        }
 )
 
-// Kept for backward compatibility. Use NewScopeHelper2 instead until we do a 
mass refactor
-func NewScopeHelper[Conn any, Scope any, Tr any](
-       basicRes context.BasicRes,
-       vld *validator.Validate,
-       connHelper *ConnectionApiHelper,
-) *ScopeApiHelper[Conn, Scope, Tr] {
-       reflectionParams := ReflectionParameters{}
-       return NewScopeHelper2[Conn, Scope, Tr](
-               basicRes,
-               vld,
-               connHelper,
-               NewScopeDatabaseHelperImpl[Conn, Scope, Tr](basicRes, 
connHelper, &reflectionParams),
-               &reflectionParams,
-               nil,
-       )
-}
-
 // NewScopeHelper creates a ScopeHelper for scopes management
-func NewScopeHelper2[Conn any, Scope any, Tr any](
+func NewScopeHelper[Conn any, Scope any, Tr any](
        basicRes context.BasicRes,
        vld *validator.Validate,
        connHelper *ConnectionApiHelper,
@@ -92,11 +75,7 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) Put(input 
*plugin.ApiResourceInput) (*
        return &plugin.ApiResourceOutput{Body: apiScopes, Status: 
http.StatusOK}, nil
 }
 
-// TODO remove fieldName param in the future and adjust plugins to use 
reflection params on init
-func (c *ScopeApiHelper[Conn, Scope, Tr]) Update(input 
*plugin.ApiResourceInput, fieldName string) (*plugin.ApiResourceOutput, 
errors.Error) {
-       if fieldName != "" {
-               c.reflectionParams.ScopeIdColumnName = fieldName //for backward 
compatibility
-       }
+func (c *ScopeApiHelper[Conn, Scope, Tr]) Update(input 
*plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
        apiScope, err := c.GenericScopeApiHelper.UpdateScope(input)
        if err != nil {
                return nil, err
@@ -112,11 +91,7 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) 
GetScopeList(input *plugin.ApiResource
        return &plugin.ApiResourceOutput{Body: scopes, Status: http.StatusOK}, 
nil
 }
 
-// TODO remove fieldName param in the future and adjust plugins to use 
reflection params on init
-func (c *ScopeApiHelper[Conn, Scope, Tr]) GetScope(input 
*plugin.ApiResourceInput, fieldName string) (*plugin.ApiResourceOutput, 
errors.Error) {
-       if fieldName != "" {
-               c.reflectionParams.ScopeIdColumnName = fieldName //for backward 
compatibility
-       }
+func (c *ScopeApiHelper[Conn, Scope, Tr]) GetScope(input 
*plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
        scope, err := c.GenericScopeApiHelper.GetScope(input)
        if err != nil {
                return nil, err
@@ -125,9 +100,9 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) GetScope(input 
*plugin.ApiResourceInpu
 }
 
 func (c *ScopeApiHelper[Conn, Scope, Tr]) Delete(input 
*plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-       bps, err := c.DeleteScope(input)
+       err := c.DeleteScope(input)
        if err != nil {
                return nil, err
        }
-       return &plugin.ApiResourceOutput{Body: bps, Status: http.StatusOK}, nil
+       return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
 }
diff --git a/backend/helpers/pluginhelper/api/scope_helper_test.go 
b/backend/helpers/pluginhelper/api/scope_helper_test.go
index e812d1ab2..86f934801 100644
--- a/backend/helpers/pluginhelper/api/scope_helper_test.go
+++ b/backend/helpers/pluginhelper/api/scope_helper_test.go
@@ -298,7 +298,7 @@ func TestScopeApiHelper_Put(t *testing.T) {
        params := &ReflectionParameters{}
        dbHelper := NewScopeDatabaseHelperImpl[TestConnection, TestRepo, 
TestTransformationRule](mockRes, connHelper, params)
        // create a mock ScopeApiHelper with a mock database connection
-       apiHelper := NewScopeHelper2[TestConnection, TestRepo, 
TestTransformationRule](mockRes, nil, connHelper, dbHelper, params, nil)
+       apiHelper := NewScopeHelper[TestConnection, TestRepo, 
TestTransformationRule](mockRes, nil, connHelper, dbHelper, params, nil)
        // test a successful call to Put
        _, err := apiHelper.Put(input)
        assert.NoError(t, err)
diff --git a/backend/plugins/bamboo/api/init.go 
b/backend/plugins/bamboo/api/init.go
index e6833f341..a99863e72 100644
--- a/backend/plugins/bamboo/api/init.go
+++ b/backend/plugins/bamboo/api/init.go
@@ -39,10 +39,18 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "ProjectKey",
+               ScopeIdColumnName: "project_key",
+       }
        scopeHelper = api.NewScopeHelper[models.BambooConnection, 
models.BambooProject, models.BambooScopeConfig](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.BambooConnection, 
models.BambooProject, models.BambooScopeConfig](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
        remoteHelper = api.NewRemoteHelper[models.BambooConnection, 
models.BambooProject, models.ApiBambooProject, api.NoRemoteGroupResponse](
                basicRes,
diff --git a/backend/plugins/bamboo/api/scope.go 
b/backend/plugins/bamboo/api/scope.go
index 0870eabb2..61c783cc0 100644
--- a/backend/plugins/bamboo/api/scope.go
+++ b/backend/plugins/bamboo/api/scope.go
@@ -59,7 +59,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "project_key")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get Bamboo projects
@@ -67,6 +67,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Description get Bamboo projects
 // @Tags plugins/bamboo
 // @Param connectionId path int false "connection ID"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -88,5 +89,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.GetScope(input, "project_key")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/bamboo
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/bamboo/impl/impl.go 
b/backend/plugins/bamboo/impl/impl.go
index dc19f9e54..d8a88d488 100644
--- a/backend/plugins/bamboo/impl/impl.go
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -225,8 +225,9 @@ func (p Bamboo) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET": api.SearchRemoteScopes,
                },
                "connections/:connectionId/scopes/:scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
        }
 }
diff --git a/backend/plugins/bitbucket/api/init.go 
b/backend/plugins/bitbucket/api/init.go
index 0337e0f8e..59a90c6a0 100644
--- a/backend/plugins/bitbucket/api/init.go
+++ b/backend/plugins/bitbucket/api/init.go
@@ -38,10 +38,18 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "BitbucketId",
+               ScopeIdColumnName: "bitbucket_id",
+       }
        scopeHelper = api.NewScopeHelper[models.BitbucketConnection, 
models.BitbucketRepo, models.BitbucketTransformationRule](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.BitbucketConnection, 
models.BitbucketRepo, models.BitbucketTransformationRule](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
        remoteHelper = api.NewRemoteHelper[models.BitbucketConnection, 
models.BitbucketRepo, models.BitbucketApiRepo, models.GroupResponse](
                basicRes,
diff --git a/backend/plugins/bitbucket/api/scope.go 
b/backend/plugins/bitbucket/api/scope.go
index 80f4a49c1..e1709bf7e 100644
--- a/backend/plugins/bitbucket/api/scope.go
+++ b/backend/plugins/bitbucket/api/scope.go
@@ -61,7 +61,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Router /plugins/bitbucket/connections/{connectionId}/scopes/{scopeId} 
[PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        input.Params["scopeId"] = strings.TrimLeft(input.Params["scopeId"], "/")
-       return scopeHelper.Update(input, "bitbucket_id")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get repos
@@ -71,6 +71,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int true "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -91,5 +92,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Router /plugins/bitbucket/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        input.Params["scopeId"] = strings.TrimLeft(input.Params["scopeId"], "/")
-       return scopeHelper.GetScope(input, "bitbucket_id")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/bitbucket
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/bitbucket/connections/{connectionId}/scopes/{scopeId} 
[DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/bitbucket/impl/impl.go 
b/backend/plugins/bitbucket/impl/impl.go
index 07037e298..569ae6064 100644
--- a/backend/plugins/bitbucket/impl/impl.go
+++ b/backend/plugins/bitbucket/impl/impl.go
@@ -204,8 +204,9 @@ func (p Bitbucket) ApiResources() 
map[string]map[string]plugin.ApiResourceHandle
                        "GET":    api.GetConnection,
                },
                "connections/:connectionId/scopes/*scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/remote-scopes": {
                        "GET": api.RemoteScopes,
diff --git a/backend/plugins/github/api/init.go 
b/backend/plugins/github/api/init.go
index 179ea7ff4..f7e1ab7b2 100644
--- a/backend/plugins/github/api/init.go
+++ b/backend/plugins/github/api/init.go
@@ -19,9 +19,12 @@ package api
 
 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/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/github/models"
        "github.com/go-playground/validator/v10"
+       "strconv"
 )
 
 var vld *validator.Validate
@@ -37,10 +40,34 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "GithubId",
+               ScopeIdColumnName: "github_id",
+               RawScopeParamName: "Name",
+       }
        scopeHelper = api.NewScopeHelper[models.GithubConnection, 
models.GithubRepo, models.GithubTransformationRule](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.GithubConnection, 
models.GithubRepo, models.GithubTransformationRule](
+                       basicRes, connectionHelper, params),
+               params,
+               &api.ScopeHelperOptions{
+                       GetScopeParamValue: func(db dal.Dal, scopeId string) 
(string, errors.Error) {
+                               id, err := 
errors.Convert01(strconv.ParseInt(scopeId, 10, 64))
+                               if err != nil {
+                                       return "", err
+                               }
+                               repo := models.GithubRepo{
+                                       GithubId: int(id),
+                               }
+                               err = db.First(&repo)
+                               if err != nil {
+                                       return "", err
+                               }
+                               return repo.Name, nil
+                       },
+               },
        )
        trHelper = 
api.NewTransformationRuleHelper[models.GithubTransformationRule](
                basicRes,
diff --git a/backend/plugins/github/api/scope.go 
b/backend/plugins/github/api/scope.go
index 1225e9edd..3346b113c 100644
--- a/backend/plugins/github/api/scope.go
+++ b/backend/plugins/github/api/scope.go
@@ -59,7 +59,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/github/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "github_id")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get Github repos
@@ -69,6 +69,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int true "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -88,5 +89,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/github/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.GetScope(input, "github_id")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/github
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/github/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/github/impl/impl.go 
b/backend/plugins/github/impl/impl.go
index 3fa51360c..c5ebaae5d 100644
--- a/backend/plugins/github/impl/impl.go
+++ b/backend/plugins/github/impl/impl.go
@@ -215,8 +215,9 @@ func (p Github) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "DELETE": api.DeleteConnection,
                },
                "connections/:connectionId/scopes/:scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/scopes": {
                        "GET": api.GetScopeList,
diff --git a/backend/plugins/gitlab/api/init.go 
b/backend/plugins/gitlab/api/init.go
index c29d34ac9..cbc336966 100644
--- a/backend/plugins/gitlab/api/init.go
+++ b/backend/plugins/gitlab/api/init.go
@@ -38,12 +38,19 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "GitlabId",
+               ScopeIdColumnName: "gitlab_id",
+       }
        scopeHelper = api.NewScopeHelper[models.GitlabConnection, 
models.GitlabProject, models.GitlabTransformationRule](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.GitlabConnection, 
models.GitlabProject, models.GitlabTransformationRule](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
-
        remoteHelper = api.NewRemoteHelper[models.GitlabConnection, 
models.GitlabProject, models.GitlabApiProject, models.GroupResponse](
                basicRes,
                vld,
diff --git a/backend/plugins/gitlab/api/scope.go 
b/backend/plugins/gitlab/api/scope.go
index a40ccf6f0..32927e559 100644
--- a/backend/plugins/gitlab/api/scope.go
+++ b/backend/plugins/gitlab/api/scope.go
@@ -59,7 +59,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/gitlab/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "gitlab_id")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get Gitlab projects
@@ -67,6 +67,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Description get Gitlab projects
 // @Tags plugins/gitlab
 // @Param connectionId path int false "connection ID"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -88,5 +89,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/gitlab/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.GetScope(input, "gitlab_id")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/gitlab
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/gitlab/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/gitlab/impl/impl.go 
b/backend/plugins/gitlab/impl/impl.go
index fb04f6207..2966c5b28 100644
--- a/backend/plugins/gitlab/impl/impl.go
+++ b/backend/plugins/gitlab/impl/impl.go
@@ -258,8 +258,9 @@ func (p Gitlab) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET":    api.GetConnection,
                },
                "connections/:connectionId/scopes/:scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/remote-scopes": {
                        "GET": api.RemoteScopes,
diff --git a/backend/plugins/jenkins/api/init.go 
b/backend/plugins/jenkins/api/init.go
index d0f125dd8..8fe30ce1e 100644
--- a/backend/plugins/jenkins/api/init.go
+++ b/backend/plugins/jenkins/api/init.go
@@ -37,10 +37,18 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "FullName",
+               ScopeIdColumnName: "full_name",
+       }
        scopeHelper = api.NewScopeHelper[models.JenkinsConnection, 
models.JenkinsJob, models.JenkinsTransformationRule](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.JenkinsConnection, 
models.JenkinsJob, models.JenkinsTransformationRule](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
        trHelper = 
api.NewTransformationRuleHelper[models.JenkinsTransformationRule](
                basicRes,
diff --git a/backend/plugins/jenkins/api/scope.go 
b/backend/plugins/jenkins/api/scope.go
index 0c932a22c..525aa52cd 100644
--- a/backend/plugins/jenkins/api/scope.go
+++ b/backend/plugins/jenkins/api/scope.go
@@ -61,7 +61,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Router /plugins/jenkins/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        input.Params["scopeId"] = strings.TrimLeft(input.Params["scopeId"], "/")
-       return scopeHelper.Update(input, "full_name")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get Jenkins jobs
@@ -71,6 +71,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int false "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -91,5 +92,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Router /plugins/jenkins/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        input.Params["scopeId"] = strings.TrimLeft(input.Params["scopeId"], "/")
-       return scopeHelper.GetScope(input, "full_name")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/jenkins
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/jenkins/connections/{connectionId}/scopes/{scopeId} 
[DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/jenkins/impl/impl.go 
b/backend/plugins/jenkins/impl/impl.go
index 104ba38fe..f60447451 100644
--- a/backend/plugins/jenkins/impl/impl.go
+++ b/backend/plugins/jenkins/impl/impl.go
@@ -183,8 +183,9 @@ func (p Jenkins) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler
                        "GET":    api.GetConnection,
                },
                "connections/:connectionId/scopes/*scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/scopes": {
                        "GET": api.GetScopeList,
diff --git a/backend/plugins/jira/api/init.go b/backend/plugins/jira/api/init.go
index eec4ee406..9944236e0 100644
--- a/backend/plugins/jira/api/init.go
+++ b/backend/plugins/jira/api/init.go
@@ -37,12 +37,19 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "BoardID",
+               ScopeIdColumnName: "board_id",
+       }
        scopeHelper = api.NewScopeHelper[models.JiraConnection, 
models.JiraBoard, models.JiraTransformationRule](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.JiraConnection, 
models.JiraBoard, models.JiraTransformationRule](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
-
        trHelper = 
api.NewTransformationRuleHelper[models.JiraTransformationRule](
                basicRes,
                vld,
diff --git a/backend/plugins/jira/api/scope.go 
b/backend/plugins/jira/api/scope.go
index 7a4fabd30..2743639eb 100644
--- a/backend/plugins/jira/api/scope.go
+++ b/backend/plugins/jira/api/scope.go
@@ -66,7 +66,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "board_id")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get Jira boards
@@ -76,6 +76,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int false "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -95,7 +96,22 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.GetScope(input, "board_id")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/jira
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/jira/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
 
 func GetApiJira(op *tasks.JiraOptions, apiClient aha.ApiClientAbstract) 
(*apiv2models.Board, errors.Error) {
diff --git a/backend/plugins/jira/impl/impl.go 
b/backend/plugins/jira/impl/impl.go
index 7955a4dc7..c1263bef9 100644
--- a/backend/plugins/jira/impl/impl.go
+++ b/backend/plugins/jira/impl/impl.go
@@ -288,8 +288,9 @@ func (p Jira) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET": api.Proxy,
                },
                "connections/:connectionId/scopes/:scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/scopes": {
                        "GET": api.GetScopeList,
diff --git a/backend/plugins/pagerduty/api/init.go 
b/backend/plugins/pagerduty/api/init.go
index 80c2fbcdd..84729850c 100644
--- a/backend/plugins/pagerduty/api/init.go
+++ b/backend/plugins/pagerduty/api/init.go
@@ -44,7 +44,7 @@ func Init(br context.BasicRes) {
                ScopeIdColumnName: "id",
                RawScopeParamName: "ScopeId",
        }
-       scopeHelper = api.NewScopeHelper2[models.PagerDutyConnection, 
models.Service, models.PagerdutyTransformationRule](
+       scopeHelper = api.NewScopeHelper[models.PagerDutyConnection, 
models.Service, models.PagerdutyTransformationRule](
                basicRes,
                vld,
                connectionHelper,
diff --git a/backend/plugins/pagerduty/api/scope.go 
b/backend/plugins/pagerduty/api/scope.go
index 7560fa570..02d7cc2cb 100644
--- a/backend/plugins/pagerduty/api/scope.go
+++ b/backend/plugins/pagerduty/api/scope.go
@@ -58,7 +58,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/pagerduty/connections/{connectionId}/scopes/{serviceId} 
[PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get PagerDuty repos
@@ -89,7 +89,7 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/pagerduty/connections/{connectionId}/scopes/{serviceId} 
[GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.GetScope(input, "")
+       return scopeHelper.GetScope(input)
 }
 
 // DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
@@ -99,7 +99,7 @@ func GetScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Param connectionId path int true "connection ID"
 // @Param serviceId path int true "service ID"
 // @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
-// @Success 200  {object} []models.Blueprint "list of blueprints impacted by 
the deletion"
+// @Success 200
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/pagerduty/connections/{connectionId}/scopes/{serviceId} 
[DELETE]
diff --git a/backend/plugins/sonarqube/api/init.go 
b/backend/plugins/sonarqube/api/init.go
index 37bf1fcfb..2a5273b04 100644
--- a/backend/plugins/sonarqube/api/init.go
+++ b/backend/plugins/sonarqube/api/init.go
@@ -37,10 +37,18 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
-       scopeHelper = api.NewScopeHelper[models.SonarqubeConnection, 
models.SonarqubeProject, interface{}](
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "ProjectKey",
+               ScopeIdColumnName: "project_key",
+       }
+       scopeHelper = api.NewScopeHelper[models.SonarqubeConnection, 
models.SonarqubeProject, any](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.SonarqubeConnection, 
models.SonarqubeProject, any](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
        remoteHelper = api.NewRemoteHelper[models.SonarqubeConnection, 
models.SonarqubeProject, models.SonarqubeApiProject, api.NoRemoteGroupResponse](
                basicRes,
diff --git a/backend/plugins/sonarqube/api/scope.go 
b/backend/plugins/sonarqube/api/scope.go
index e2946eec3..96184a9c2 100644
--- a/backend/plugins/sonarqube/api/scope.go
+++ b/backend/plugins/sonarqube/api/scope.go
@@ -59,7 +59,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/sonarqube/connections/{connectionId}/scopes/{scopeId} 
[PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "project_key")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get Sonarqube projects
@@ -69,6 +69,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int false "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -88,5 +89,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/sonarqube/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.GetScope(input, "project_key")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/sonarqube
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/sonarqube/connections/{connectionId}/scopes/{scopeId} 
[DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/sonarqube/impl/impl.go 
b/backend/plugins/sonarqube/impl/impl.go
index 0457685e3..a63d0d273 100644
--- a/backend/plugins/sonarqube/impl/impl.go
+++ b/backend/plugins/sonarqube/impl/impl.go
@@ -173,8 +173,9 @@ func (p Sonarqube) ApiResources() 
map[string]map[string]plugin.ApiResourceHandle
                        "GET": api.SearchRemoteScopes,
                },
                "connections/:connectionId/scopes/:scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/scopes": {
                        "GET": api.GetScopeList,
diff --git a/backend/plugins/tapd/api/init.go b/backend/plugins/tapd/api/init.go
index 267042567..42cf79f91 100644
--- a/backend/plugins/tapd/api/init.go
+++ b/backend/plugins/tapd/api/init.go
@@ -38,10 +38,18 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       params := &api.ReflectionParameters{
+               ScopeIdFieldName:  "Id",
+               ScopeIdColumnName: "id",
+       }
        scopeHelper = api.NewScopeHelper[models.TapdConnection, 
models.TapdWorkspace, models.TapdTransformationRule](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.TapdConnection, 
models.TapdWorkspace, models.TapdTransformationRule](
+                       basicRes, connectionHelper, params),
+               params,
+               nil,
        )
        remoteHelper = api.NewRemoteHelper[models.TapdConnection, 
models.TapdWorkspace, models.TapdWorkspace, api.BaseRemoteGroupResponse](
                basicRes,
diff --git a/backend/plugins/tapd/api/scope.go 
b/backend/plugins/tapd/api/scope.go
index d5d41d828..5ed0bd109 100644
--- a/backend/plugins/tapd/api/scope.go
+++ b/backend/plugins/tapd/api/scope.go
@@ -60,7 +60,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/tapd/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input, "id")
+       return scopeHelper.Update(input)
 }
 
 // GetScopeList get tapd jobs
@@ -70,6 +70,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param connectionId path int false "connection ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
 // @Success 200  {object} []ScopeRes
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
@@ -90,5 +91,20 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Router /plugins/tapd/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        input.Params["scopeId"] = strings.TrimLeft(input.Params["scopeId"], "/")
-       return scopeHelper.GetScope(input, "id")
+       return scopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/tapd
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/tapd/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return scopeHelper.Delete(input)
 }
diff --git a/backend/plugins/tapd/impl/impl.go 
b/backend/plugins/tapd/impl/impl.go
index 97ed805af..5bf0e7f1f 100644
--- a/backend/plugins/tapd/impl/impl.go
+++ b/backend/plugins/tapd/impl/impl.go
@@ -278,8 +278,9 @@ func (p Tapd) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET": api.Proxy,
                },
                "connections/:connectionId/scopes/:scopeId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/remote-scopes-prepare-token": {
                        "GET": api.PrepareFirstPageToken,
diff --git a/backend/plugins/trello/api/scope.go 
b/backend/plugins/trello/api/scope.go
index 7f9675780..3c2769612 100644
--- a/backend/plugins/trello/api/scope.go
+++ b/backend/plugins/trello/api/scope.go
@@ -197,6 +197,22 @@ func GetScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
        return &plugin.ApiResourceOutput{Body: apiBoard{board, rule.Name}, 
Status: http.StatusOK}, nil
 }
 
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/trello
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/trello/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       // This plugin needs to be redone in terms of helpers later
+       return &plugin.ApiResourceOutput{Status: http.StatusNotFound}, nil
+}
+
 func extractParam(params map[string]string) (uint64, string) {
        connectionId, _ := strconv.ParseUint(params["connectionId"], 10, 64)
        return connectionId, params["boardId"]
diff --git a/backend/plugins/trello/impl/impl.go 
b/backend/plugins/trello/impl/impl.go
index 4ac8730fb..2d508d340 100644
--- a/backend/plugins/trello/impl/impl.go
+++ b/backend/plugins/trello/impl/impl.go
@@ -167,8 +167,9 @@ func (p Trello) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "GET":   api.GetTransformationRule,
                },
                "connections/:connectionId/scopes/:boardId": {
-                       "GET":   api.GetScope,
-                       "PATCH": api.UpdateScope,
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/scopes": {
                        "GET": api.GetScopeList,
diff --git a/backend/plugins/zentao/api/init.go 
b/backend/plugins/zentao/api/init.go
index 1f3f51bb2..440ea3e56 100644
--- a/backend/plugins/zentao/api/init.go
+++ b/backend/plugins/zentao/api/init.go
@@ -45,15 +45,31 @@ func Init(br context.BasicRes) {
                basicRes,
                vld,
        )
+       productParams := &api.ReflectionParameters{
+               ScopeIdFieldName:  "Id",
+               ScopeIdColumnName: "id",
+       }
        productScopeHelper = api.NewScopeHelper[models.ZentaoConnection, 
models.ZentaoProduct, api.NoTransformation](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.ZentaoConnection, 
models.ZentaoProduct, api.NoTransformation](
+                       basicRes, connectionHelper, productParams),
+               productParams,
+               nil,
        )
+       projectParams := &api.ReflectionParameters{
+               ScopeIdFieldName:  "Project",
+               ScopeIdColumnName: "project",
+       }
        projectScopeHelper = api.NewScopeHelper[models.ZentaoConnection, 
models.ZentaoProject, api.NoTransformation](
                basicRes,
                vld,
                connectionHelper,
+               api.NewScopeDatabaseHelperImpl[models.ZentaoConnection, 
models.ZentaoProject, api.NoTransformation](
+                       basicRes, connectionHelper, projectParams),
+               projectParams,
+               nil,
        )
        productRemoteHelper = api.NewRemoteHelper[models.ZentaoConnection, 
models.ZentaoProduct, models.ZentaoProductRes, api.BaseRemoteGroupResponse](
                basicRes,
diff --git a/backend/plugins/zentao/api/scope.go 
b/backend/plugins/zentao/api/scope.go
index 974ec8ec0..f2a03019b 100644
--- a/backend/plugins/zentao/api/scope.go
+++ b/backend/plugins/zentao/api/scope.go
@@ -81,7 +81,7 @@ func PutProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/zentao/connections/{connectionId}/scopes/product/{scopeId} 
[PATCH]
 func UpdateProductScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return productScopeHelper.Update(input, "id")
+       return productScopeHelper.Update(input)
 }
 
 // UpdateProjectScope patch to zentao project
@@ -97,7 +97,7 @@ func UpdateProductScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutp
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/zentao/connections/{connectionId}/scopes/project/{scopeId} 
[PATCH]
 func UpdateProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return projectScopeHelper.Update(input, "id")
+       return projectScopeHelper.Update(input)
 }
 
 // TODO GetScopeList get zentao projects and products
@@ -113,7 +113,7 @@ func UpdateProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutp
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/zentao/connections/{connectionId}/scopes/product/{scopeId} 
[GET]
 func GetProductScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return productScopeHelper.GetScope(input, "id")
+       return productScopeHelper.GetScope(input)
 }
 
 // GetProjectScope get one project
@@ -127,5 +127,35 @@ func GetProductScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/zentao/connections/{connectionId}/scopes/project/{scopeId} 
[GET]
 func GetProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return projectScopeHelper.GetScope(input, "id")
+       return projectScopeHelper.GetScope(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/zentao
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/zentao/connections/{connectionId}/scopes/product/{scopeId} 
[DELETE]
+func DeleteProductScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       return productScopeHelper.Delete(input)
+}
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/zentao
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/zentao/connections/{connectionId}/scopes/project/{scopeId} 
[DELETE]
+func DeleteProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       return projectScopeHelper.Delete(input)
 }
diff --git a/backend/plugins/zentao/impl/impl.go 
b/backend/plugins/zentao/impl/impl.go
index 56b8257e0..9433d0919 100644
--- a/backend/plugins/zentao/impl/impl.go
+++ b/backend/plugins/zentao/impl/impl.go
@@ -164,12 +164,14 @@ func (p Zentao) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "PUT": api.PutProjectScope,
                },
                "connections/:connectionId/scopes/product/:scopeId": {
-                       "GET":   api.GetProductScope,
-                       "PATCH": api.UpdateProductScope,
+                       "GET":    api.GetProductScope,
+                       "PATCH":  api.UpdateProductScope,
+                       "DELETE": api.DeleteProductScope,
                },
                "connections/:connectionId/scopes/project/:scopeId": {
-                       "GET":   api.GetProjectScope,
-                       "PATCH": api.UpdateProjectScope,
+                       "GET":    api.GetProjectScope,
+                       "PATCH":  api.UpdateProjectScope,
+                       "DELETE": api.DeleteProjectScope,
                },
                "connections/:connectionId/remote-scopes": {
                        "GET": api.RemoteScopes,
diff --git a/backend/server/services/remote/plugin/scope_api.go 
b/backend/server/services/remote/plugin/scope_api.go
index 77fd71a11..0d1839fca 100644
--- a/backend/server/services/remote/plugin/scope_api.go
+++ b/backend/server/services/remote/plugin/scope_api.go
@@ -94,11 +94,11 @@ func (pa *pluginAPI) GetScope(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
 }
 
 func (pa *pluginAPI) DeleteScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       bps, err := scopeHelper.DeleteScope(input)
+       err := scopeHelper.DeleteScope(input)
        if err != nil {
                return nil, err
        }
-       return &plugin.ApiResourceOutput{Body: bps, Status: http.StatusOK}, nil
+       return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
 }
 
 // convertScopeResponse adapt the "remote" scopes to a serializable 
api.ScopeRes

Reply via email to