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

hez 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 c92cf69a6 [fix-5085]: Azure bug fixes related to external repo support 
(#5448)
c92cf69a6 is described below

commit c92cf69a63589ba34cafea26d9d3687c7afb4fe4
Author: Keon Amini <[email protected]>
AuthorDate: Mon Jun 12 13:35:34 2023 -0500

    [fix-5085]: Azure bug fixes related to external repo support (#5448)
    
    * fix: Match scope ids containing "/" in scope API
    
    The string formats of scope ids are defined by external tools.
    Some of them might contain characters such as "/".
    
    * fix: Fix failing test
    
    This test is skip in CI because it needs a token.
    
    * fix: Fix build collection for non-external repositories
    
    Non-external repositories don't have a provider field because they are 
fetched from a different endpoint than external repositories that doesn't 
contain this information.
    Set 'tfsgit' as default provider for those repos.
    
    ---------
    
    Co-authored-by: Camille Teruel <[email protected]>
---
 .../pluginhelper/api/scope_generic_helper.go       | 101 +++++++++++----------
 .../python/plugins/azuredevops/azuredevops/main.py |   8 +-
 .../azuredevops/azuredevops/streams/builds.py      |   2 +-
 .../python/pydevlake/pydevlake/testing/testing.py  |   1 -
 .../server/services/remote/plugin/default_api.go   |   3 +-
 5 files changed, 60 insertions(+), 55 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/scope_generic_helper.go 
b/backend/helpers/pluginhelper/api/scope_generic_helper.go
index 5a5e21257..d6b045564 100644
--- a/backend/helpers/pluginhelper/api/scope_generic_helper.go
+++ b/backend/helpers/pluginhelper/api/scope_generic_helper.go
@@ -135,11 +135,11 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
DbHelper() ScopeDatabaseHelper[
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) PutScopes(input 
*plugin.ApiResourceInput, scopes []*Scope) ([]*ScopeRes[Scope, Tr], 
errors.Error) {
-       params := c.extractFromReqParam(input)
-       if params.connectionId == 0 {
-               return nil, errors.BadInput.New("invalid connectionId")
+       params, err := c.extractFromReqParam(input, false)
+       if err != nil {
+               return nil, err
        }
-       err := c.dbHelper.VerifyConnection(params.connectionId)
+       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))
        }
@@ -171,14 +171,11 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
PutScopes(input *plugin.ApiReso
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) UpdateScope(input 
*plugin.ApiResourceInput) (*ScopeRes[Scope, Tr], errors.Error) {
-       params := c.extractFromReqParam(input)
-       if params.connectionId == 0 {
-               return nil, errors.BadInput.New("invalid connectionId")
-       }
-       if len(params.scopeId) == 0 {
-               return nil, errors.BadInput.New("invalid scopeId")
+       params, err := c.extractFromReqParam(input, true)
+       if err != nil {
+               return nil, err
        }
-       err := c.dbHelper.VerifyConnection(params.connectionId)
+       err = c.dbHelper.VerifyConnection(params.connectionId)
        if err != nil {
                return nil, err
        }
@@ -206,11 +203,11 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
UpdateScope(input *plugin.ApiRe
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) GetScopes(input 
*plugin.ApiResourceInput) ([]*ScopeRes[Scope, Tr], errors.Error) {
-       params := c.extractFromGetReqParam(input)
-       if params.connectionId == 0 {
+       params, err := c.extractFromGetReqParam(input, false)
+       if err != nil {
                return nil, errors.BadInput.New("invalid path params: 
\"connectionId\" not set")
        }
-       err := c.dbHelper.VerifyConnection(params.connectionId)
+       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))
        }
@@ -253,14 +250,11 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
GetScopes(input *plugin.ApiReso
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) GetScope(input 
*plugin.ApiResourceInput) (*ScopeRes[Scope, Tr], errors.Error) {
-       params := c.extractFromGetReqParam(input)
-       if params == nil || params.connectionId == 0 {
-               return nil, 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")
+       params, err := c.extractFromGetReqParam(input, true)
+       if err != nil {
+               return nil, err
        }
-       err := c.dbHelper.VerifyConnection(params.connectionId)
+       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))
        }
@@ -286,14 +280,11 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
GetScope(input *plugin.ApiResou
 }
 
 func (c *GenericScopeApiHelper[Conn, Scope, Tr]) DeleteScope(input 
*plugin.ApiResourceInput) errors.Error {
-       params := c.extractFromDeleteReqParam(input)
-       if params == nil || params.connectionId == 0 {
-               return errors.BadInput.New("invalid path params: 
\"connectionId\" not set")
-       }
-       if len(params.scopeId) == 0 || params.scopeId == "0" {
-               return errors.BadInput.New("invalid path params: \"scopeId\" 
not set/invalid")
+       params, err := c.extractFromDeleteReqParam(input)
+       if err != nil {
+               return err
        }
-       err := c.dbHelper.VerifyConnection(params.connectionId)
+       err = c.dbHelper.VerifyConnection(params.connectionId)
        if err != nil {
                return errors.Default.Wrap(err, fmt.Sprintf("error verifying 
connection for connection ID %d", params.connectionId))
        }
@@ -357,56 +348,70 @@ func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
mapByScopeId(scopes []*ScopeRes
        return scopeMap
 }
 
-func (c *GenericScopeApiHelper[Conn, Scope, Tr]) extractFromReqParam(input 
*plugin.ApiResourceInput) *requestParams {
+func (c *GenericScopeApiHelper[Conn, Scope, Tr]) extractFromReqParam(input 
*plugin.ApiResourceInput, withScopeId bool) (*requestParams, errors.Error) {
        connectionId, err := strconv.ParseUint(input.Params["connectionId"], 
10, 64)
-       if err != nil || connectionId == 0 {
-               connectionId = 0
+       if err != nil {
+               return nil, errors.BadInput.Wrap(err, "Invalid 
\"connectionId\"")
+       }
+       if connectionId == 0 {
+               return nil, errors.BadInput.New("\"connectionId\" cannot be 0")
+       }
+       var scopeId string
+       if withScopeId {
+               scopeId = input.Params["scopeId"]
+               // Path params that use `/*param` handlers instead of `/:param` 
start with a /, so remove it
+               if scopeId[0] == '/' {
+                       scopeId = scopeId[1:]
+               }
        }
-       scopeId := input.Params["scopeId"]
        pluginName := input.Params["plugin"]
        return &requestParams{
                connectionId: connectionId,
-               scopeId:      scopeId,
                plugin:       pluginName,
-       }
+               scopeId:      scopeId,
+       }, nil
 }
 
-func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
extractFromDeleteReqParam(input *plugin.ApiResourceInput) *deleteRequestParams {
-       params := c.extractFromReqParam(input)
-       var err errors.Error
+func (c *GenericScopeApiHelper[Conn, Scope, Tr]) 
extractFromDeleteReqParam(input *plugin.ApiResourceInput) 
(*deleteRequestParams, errors.Error) {
+       params, err := c.extractFromReqParam(input, true)
+       if err != nil {
+               return nil, err
+       }
        var deleteDataOnly bool
        {
                ddo, ok := input.Query["delete_data_only"]
                if ok {
                        deleteDataOnly, err = 
errors.Convert01(strconv.ParseBool(ddo[0]))
-               }
-               if err != nil {
-                       deleteDataOnly = false
+                       if err != nil {
+                               deleteDataOnly = false
+                       }
                }
        }
        return &deleteRequestParams{
                requestParams:  *params,
                deleteDataOnly: deleteDataOnly,
-       }
+       }, nil
 }
 
-func (c *GenericScopeApiHelper[Conn, Scope, Tr]) extractFromGetReqParam(input 
*plugin.ApiResourceInput) *getRequestParams {
-       params := c.extractFromReqParam(input)
-       var err errors.Error
+func (c *GenericScopeApiHelper[Conn, Scope, Tr]) extractFromGetReqParam(input 
*plugin.ApiResourceInput, withScopeId bool) (*getRequestParams, errors.Error) {
+       params, err := c.extractFromReqParam(input, withScopeId)
+       if err != nil {
+               return nil, err
+       }
        var loadBlueprints bool
        {
                lbps, ok := input.Query["blueprints"]
                if ok {
                        loadBlueprints, err = 
errors.Convert01(strconv.ParseBool(lbps[0]))
-               }
-               if err != nil {
-                       loadBlueprints = false
+                       if err != nil {
+                               loadBlueprints = false
+                       }
                }
        }
        return &getRequestParams{
                requestParams:  *params,
                loadBlueprints: loadBlueprints,
-       }
+       }, nil
 }
 
 func setScopeFields(p interface{}, connectionId uint64, createdDate 
*time.Time, updatedDate *time.Time) {
diff --git a/backend/python/plugins/azuredevops/azuredevops/main.py 
b/backend/python/plugins/azuredevops/azuredevops/main.py
index 14172ac1c..15d2593c2 100644
--- a/backend/python/plugins/azuredevops/azuredevops/main.py
+++ b/backend/python/plugins/azuredevops/azuredevops/main.py
@@ -22,7 +22,7 @@ from azuredevops.streams.jobs import Jobs
 from azuredevops.streams.pull_request_commits import GitPullRequestCommits
 from azuredevops.streams.pull_requests import GitPullRequests
 
-from pydevlake import Plugin, RemoteScopeGroup, DomainType, ScopeConfigPair, 
TestConnectionResult
+from pydevlake import Plugin, RemoteScopeGroup, DomainType, 
TestConnectionResult
 from pydevlake.domain_layer.code import Repo
 from pydevlake.domain_layer.devops import CicdScope
 from pydevlake.pipeline_tasks import gitextractor, refdiff
@@ -124,14 +124,14 @@ class AzureDevOpsPlugin(Plugin):
         return TestConnectionResult.from_api_response(res, message)
 
     def extra_tasks(self, scope: GitRepository, scope_config: 
GitRepositoryConfig, connection: AzureDevOpsConnection):
-        if DomainType.CODE in scope_config.entity_types and not 
scope.is_external():
+        if DomainType.CODE in scope_config.domain_types and not 
scope.is_external():
             url = urlparse(scope.remote_url)
             url = 
url._replace(netloc=f'{url.username}:{connection.token.get_secret_value()}@{url.hostname}')
             yield gitextractor(url.geturl(), scope.domain_id(), 
connection.proxy)
 
-    def extra_stages(self, scope_config_pairs: list[ScopeConfigPair], _):
+    def extra_stages(self, scope_config_pairs: list[tuple[GitRepository, 
GitRepositoryConfig]], _):
         for scope, config in scope_config_pairs:
-            if DomainType.CODE in config.entity_types:
+            if DomainType.CODE in config.domain_types:
                 if not scope.is_external():
                     yield [refdiff(scope.id, config.refdiff)]
 
diff --git a/backend/python/plugins/azuredevops/azuredevops/streams/builds.py 
b/backend/python/plugins/azuredevops/azuredevops/streams/builds.py
index 46d0a9ea9..9b6deda78 100644
--- a/backend/python/plugins/azuredevops/azuredevops/streams/builds.py
+++ b/backend/python/plugins/azuredevops/azuredevops/streams/builds.py
@@ -29,7 +29,7 @@ class Builds(Stream):
     def collect(self, state, context) -> Iterable[tuple[object, dict]]:
         repo: GitRepository = context.scope
         api = AzureDevOpsAPI(context.connection)
-        response = api.builds(repo.org_id, repo.project_id, repo.id, 
repo.provider)
+        response = api.builds(repo.org_id, repo.project_id, repo.id, 
repo.provider or 'tfsgit')
         for raw_build in response:
             yield raw_build, state
 
diff --git a/backend/python/pydevlake/pydevlake/testing/testing.py 
b/backend/python/pydevlake/pydevlake/testing/testing.py
index fb2a990a1..e31240e1c 100644
--- a/backend/python/pydevlake/pydevlake/testing/testing.py
+++ b/backend/python/pydevlake/pydevlake/testing/testing.py
@@ -172,7 +172,6 @@ def assert_valid_remote_scopes(plugin: Plugin, connection: 
Connection, group_id:
 def assert_valid_pipeline_plan(plugin: Plugin, connection: Connection, 
tool_scope: ToolScope, scope_config: ScopeConfig) -> list[list[PipelineTask]]:
     plan = plugin.make_pipeline_plan(
         [(tool_scope, scope_config)],
-        [domain_type.value for domain_type in DomainType],
         connection
     )
     assert len(plan) > 0, 'Pipeline plan has no stage'
diff --git a/backend/server/services/remote/plugin/default_api.go 
b/backend/server/services/remote/plugin/default_api.go
index 817a05d4f..ac100c67f 100644
--- a/backend/server/services/remote/plugin/default_api.go
+++ b/backend/server/services/remote/plugin/default_api.go
@@ -65,7 +65,8 @@ func GetDefaultAPI(
                        "PUT": papi.PutScope,
                        "GET": papi.ListScopes,
                },
-               "connections/:connectionId/scopes/:scopeId": {
+               // Use `*` to match scopeId with `/` in it
+               "connections/:connectionId/scopes/*scopeId": {
                        "GET":    papi.GetScope,
                        "PATCH":  papi.UpdateScope,
                        "DELETE": papi.DeleteScope,

Reply via email to