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 dca268280 Bamboo scopes and remote api (#4461)
dca268280 is described below

commit dca268280fae478f5433a2589e34489458961c0c
Author: mappjzc <[email protected]>
AuthorDate: Tue Feb 21 20:43:42 2023 +0800

    Bamboo scopes and remote api (#4461)
    
    * feat: bamboo blueprint
    
    Add bamboo blueprint.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for merge
    
    fix for merge
    
    Nddtfjiang <[email protected]>
    
    * feat: add scopes and remote
    
    Add scopes and remote api for bamboo
    
    Nddtfjiang <[email protected]>
    
    * feat: scope and remote api for bamboo
    
    Add scope and remote api for bamboo
    
    Nddtfjiang <[email protected]>
---
 backend/plugins/bamboo/api/blueprint_V200_test.go  |  11 +-
 backend/plugins/bamboo/api/blueprint_v200.go       |   8 +-
 backend/plugins/bamboo/api/remote.go               | 302 +++++++++++++++++++++
 backend/plugins/{sonarqube => bamboo}/api/scope.go | 153 +++++++----
 backend/plugins/bamboo/impl/impl.go                |  15 +
 backend/plugins/bamboo/models/job.go               |  21 +-
 .../migrationscripts/20230216_add_init_tables.go   |   2 +-
 .../models/migrationscripts/archived/project.go    |  16 +-
 backend/plugins/bamboo/models/plan.go              |  39 ++-
 backend/plugins/bamboo/models/project.go           |  19 ++
 backend/plugins/bamboo/tasks/job_extractor.go      |   4 +-
 backend/plugins/bamboo/tasks/plan_extractor.go     |   4 +-
 backend/plugins/sonarqube/api/scope.go             |   4 +-
 13 files changed, 496 insertions(+), 102 deletions(-)

diff --git a/backend/plugins/bamboo/api/blueprint_V200_test.go 
b/backend/plugins/bamboo/api/blueprint_V200_test.go
index 3622b93c2..e9f1f4b43 100644
--- a/backend/plugins/bamboo/api/blueprint_V200_test.go
+++ b/backend/plugins/bamboo/api/blueprint_V200_test.go
@@ -30,6 +30,7 @@ import (
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/helpers/unithelper"
        "github.com/apache/incubator-devlake/plugins/bamboo/models"
+       "github.com/apache/incubator-devlake/plugins/bamboo/tasks"
        "github.com/go-playground/validator/v10"
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/mock"
@@ -94,13 +95,17 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
 
        var expectRepoId = "bamboo:BambooProject:1:TEST"
 
-       var testSubTaskMeta = []plugin.SubTaskMeta{}
+       var testSubTaskMeta = []plugin.SubTaskMeta{
+               tasks.ConvertProjectsMeta,
+       }
 
        var expectPlans = plugin.PipelinePlan{
                {
                        {
-                               Plugin:   "bamboo",
-                               Subtasks: []string{},
+                               Plugin: "bamboo",
+                               Subtasks: []string{
+                                       tasks.ConvertProjectsMeta.Name,
+                               },
                                Options: map[string]interface{}{
                                        "connectionId":         uint64(1),
                                        "projectKey":           testKey,
diff --git a/backend/plugins/bamboo/api/blueprint_v200.go 
b/backend/plugins/bamboo/api/blueprint_v200.go
index d627b0973..d7fd92815 100644
--- a/backend/plugins/bamboo/api/blueprint_v200.go
+++ b/backend/plugins/bamboo/api/blueprint_v200.go
@@ -67,7 +67,7 @@ func makeScopeV200(connectionId uint64, scopes 
[]*plugin.BlueprintScopeV200) ([]
                id := 
didgen.NewDomainIdGenerator(&models.BambooProject{}).Generate(connectionId, 
scope.Id)
 
                // get project from db
-               BambooProject, err := 
GetprojectByConnectionIdAndscopeId(connectionId, scope.Id)
+               BambooProject, err := 
GetProjectByConnectionIdAndscopeId(connectionId, scope.Id)
                if err != nil {
                        return nil, err
                }
@@ -93,7 +93,7 @@ func makePipelinePlanV200(
                var stage plugin.PipelineStage
                var err errors.Error
                // get project
-               project, err := 
GetprojectByConnectionIdAndscopeId(connection.ID, scope.Id)
+               project, err := 
GetProjectByConnectionIdAndscopeId(connection.ID, scope.Id)
                if err != nil {
                        return nil, err
                }
@@ -127,8 +127,8 @@ func makePipelinePlanV200(
        return plans, nil
 }
 
-// GetprojectByConnectionIdAndscopeId get tbe project by the connectionId and 
the scopeId
-func GetprojectByConnectionIdAndscopeId(connectionId uint64, scopeId string) 
(*models.BambooProject, errors.Error) {
+// GetProjectByConnectionIdAndscopeId get tbe project by the connectionId and 
the scopeId
+func GetProjectByConnectionIdAndscopeId(connectionId uint64, scopeId string) 
(*models.BambooProject, errors.Error) {
        key := scopeId
        project := &models.BambooProject{}
        db := basicRes.GetDal()
diff --git a/backend/plugins/bamboo/api/remote.go 
b/backend/plugins/bamboo/api/remote.go
new file mode 100644
index 000000000..250571390
--- /dev/null
+++ b/backend/plugins/bamboo/api/remote.go
@@ -0,0 +1,302 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+       "context"
+       "encoding/base64"
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "net/url"
+       "strconv"
+
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/bamboo/models"
+)
+
+type RemoteScopesChild struct {
+       Type     string      `json:"type"`
+       ParentId *string     `json:"parentId"`
+       Id       string      `json:"id"`
+       Name     string      `json:"name"`
+       Data     interface{} `json:"data"`
+}
+
+type RemoteScopesOutput struct {
+       Children      []RemoteScopesChild `json:"children"`
+       NextPageToken string              `json:"nextPageToken"`
+}
+
+type SearchRemoteScopesOutput struct {
+       Children []RemoteScopesChild `json:"children"`
+       Page     int                 `json:"page"`
+       PageSize int                 `json:"pageSize"`
+}
+
+type PageData struct {
+       Page     int `json:"page"`
+       PageSize int `json:"per_page"`
+}
+
+const BambooRemoteScopesPerPage int = 100
+const TypeProject string = "scope"
+const TypeGroup string = "group"
+
+// RemoteScopes list all available scope for users
+// @Summary list all available scope for users
+// @Description list all available scope for users
+// @Tags plugins/bamboo
+// @Accept application/json
+// @Param connectionId path int false "connection ID"
+// @Param groupId query string false "group ID"
+// @Param pageToken query string false "page Token"
+// @Success 200  {object} RemoteScopesOutput
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/bamboo/connections/{connectionId}/remote-scopes [GET]
+func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       connectionId, _ := extractParam(input.Params)
+       if connectionId == 0 {
+               return nil, errors.BadInput.New("invalid connectionId")
+       }
+
+       connection := &models.BambooConnection{}
+       err := connectionHelper.First(connection, input.Params)
+       if err != nil {
+               return nil, err
+       }
+
+       pageToken, ok := input.Query["pageToken"]
+       if !ok || len(pageToken) == 0 {
+               pageToken = []string{""}
+       }
+
+       // get pageData
+       pageData, err := GetPageDataFromPageToken(pageToken[0])
+       if err != nil {
+               return nil, errors.BadInput.New("failed to get paget token")
+       }
+
+       // create api client
+       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, connection)
+       if err != nil {
+               return nil, err
+       }
+
+       var res *http.Response
+       outputBody := &RemoteScopesOutput{}
+
+       query := GetQueryFromPageData(pageData)
+
+       res, err = apiClient.Get("/project.json", query, nil)
+
+       if err != nil {
+               return nil, err
+       }
+
+       resBody := models.ApiBambooProjectResponse{}
+       err = api.UnmarshalResponse(res, &resBody)
+       if err != nil {
+               return nil, err
+       }
+
+       // append project to output
+       for _, apiProject := range resBody.Projects.Projects {
+               project := &models.BambooProject{}
+               project.Convert(&apiProject)
+               child := RemoteScopesChild{
+                       Type:     TypeProject,
+                       ParentId: nil,
+                       Id:       project.ProjectKey,
+                       Name:     project.Name,
+                       Data:     project,
+               }
+
+               outputBody.Children = append(outputBody.Children, child)
+       }
+
+       // check project count
+       if len(resBody.Projects.Projects) < pageData.PageSize {
+               pageData = nil
+       }
+
+       // get the next page token
+       outputBody.NextPageToken = ""
+       if pageData != nil {
+               pageData.Page += 1
+               pageData.PageSize = BambooRemoteScopesPerPage
+
+               outputBody.NextPageToken, err = 
GetPageTokenFromPageData(pageData)
+               if err != nil {
+                       return nil, err
+               }
+       }
+
+       return &plugin.ApiResourceOutput{Body: outputBody, Status: 
http.StatusOK}, nil
+}
+
+// SearchRemoteScopes use the Search API and only return project
+// @Summary use the Search API and only return project
+// @Description use the Search API and only return project
+// @Tags plugins/bamboo
+// @Accept application/json
+// @Param connectionId path int false "connection ID"
+// @Param search query string false "search"
+// @Param page query int false "page number"
+// @Param pageSize query int false "page size per page"
+// @Success 200  {object} SearchRemoteScopesOutput
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/bamboo/connections/{connectionId}/search-remote-scopes 
[GET]
+func SearchRemoteScopes(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       connectionId, _ := extractParam(input.Params)
+       if connectionId == 0 {
+               return nil, errors.BadInput.New("invalid connectionId")
+       }
+
+       connection := &models.BambooConnection{}
+       err := connectionHelper.First(connection, input.Params)
+       if err != nil {
+               return nil, err
+       }
+
+       search, ok := input.Query["search"]
+       if !ok || len(search) == 0 {
+               search = []string{""}
+       }
+
+       var p int
+       var err1 error
+       page, ok := input.Query["page"]
+       if !ok || len(page) == 0 {
+               p = 1
+       } else {
+               p, err1 = strconv.Atoi(page[0])
+               if err != nil {
+                       return nil, errors.BadInput.Wrap(err1, 
fmt.Sprintf("failed to Atoi page:%s", page[0]))
+               }
+       }
+       var ps int
+       pageSize, ok := input.Query["pageSize"]
+       if !ok || len(pageSize) == 0 {
+               ps = BambooRemoteScopesPerPage
+       } else {
+               ps, err1 = strconv.Atoi(pageSize[0])
+               if err1 != nil {
+                       return nil, errors.BadInput.Wrap(err1, 
fmt.Sprintf("failed to Atoi pageSize:%s", pageSize[0]))
+               }
+       }
+       // create api client
+       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, connection)
+       if err != nil {
+               return nil, err
+       }
+
+       // set query
+       query := GetQueryForSearchProject(search[0], p, ps)
+
+       // request search
+       res, err := apiClient.Get("search/projects.json", query, nil)
+       if err != nil {
+               return nil, err
+       }
+       resBody := models.ApiBambooSearchProjectResponse{}
+       err = api.UnmarshalResponse(res, &resBody)
+       if err != nil {
+               return nil, err
+       }
+
+       outputBody := &SearchRemoteScopesOutput{}
+
+       // append project to output
+       for _, apiResult := range resBody.SearchResults {
+               var project models.BambooProject
+               apiProject, err := GetApiProject(apiResult.SearchEntity.Key, 
apiClient)
+               if err != nil {
+                       return nil, err
+               }
+
+               project.Convert(apiProject)
+               child := RemoteScopesChild{
+                       Type:     TypeProject,
+                       Id:       project.ProjectKey,
+                       ParentId: nil,
+                       Name:     project.Name,
+                       Data:     project,
+               }
+
+               outputBody.Children = append(outputBody.Children, child)
+       }
+
+       outputBody.Page = p
+       outputBody.PageSize = ps
+
+       return &plugin.ApiResourceOutput{Body: outputBody, Status: 
http.StatusOK}, nil
+}
+
+func GetPageTokenFromPageData(pageData *PageData) (string, errors.Error) {
+       // Marshal json
+       pageTokenDecode, err := json.Marshal(pageData)
+       if err != nil {
+               return "", errors.Default.Wrap(err, fmt.Sprintf("Marshal 
pageToken failed %+v", pageData))
+       }
+
+       // Encode pageToken Base64
+       return base64.StdEncoding.EncodeToString(pageTokenDecode), nil
+}
+
+func GetPageDataFromPageToken(pageToken string) (*PageData, errors.Error) {
+       if pageToken == "" {
+               return &PageData{
+                       Page:     1,
+                       PageSize: BambooRemoteScopesPerPage,
+               }, nil
+       }
+
+       // Decode pageToken Base64
+       pageTokenDecode, err := base64.StdEncoding.DecodeString(pageToken)
+       if err != nil {
+               return nil, errors.Default.Wrap(err, fmt.Sprintf("decode 
pageToken failed %s", pageToken))
+       }
+       // Unmarshal json
+       pt := &PageData{}
+       err = json.Unmarshal(pageTokenDecode, pt)
+       if err != nil {
+               return nil, errors.Default.Wrap(err, fmt.Sprintf("json 
Unmarshal pageTokenDecode failed %s", pageTokenDecode))
+       }
+
+       return pt, nil
+}
+
+func GetQueryFromPageData(pageData *PageData) url.Values {
+       query := url.Values{}
+       query.Set("showEmpty", fmt.Sprintf("%v", true))
+       query.Set("max-result", fmt.Sprintf("%v", pageData.PageSize))
+       query.Set("start-index", fmt.Sprintf("%v", 
(pageData.Page-1)*pageData.PageSize))
+       return query
+}
+
+func GetQueryForSearchProject(search string, page int, perPage int) url.Values 
{
+       query := GetQueryFromPageData(&PageData{Page: page, PageSize: perPage})
+       query.Set("searchTerm", search)
+
+       return query
+}
diff --git a/backend/plugins/sonarqube/api/scope.go 
b/backend/plugins/bamboo/api/scope.go
similarity index 54%
copy from backend/plugins/sonarqube/api/scope.go
copy to backend/plugins/bamboo/api/scope.go
index 063020133..703f20a39 100644
--- a/backend/plugins/sonarqube/api/scope.go
+++ b/backend/plugins/bamboo/api/scope.go
@@ -18,6 +18,7 @@ limitations under the License.
 package api
 
 import (
+       "context"
        "net/http"
        "strconv"
 
@@ -25,34 +26,40 @@ import (
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
-       "github.com/apache/incubator-devlake/plugins/sonarqube/models"
+       "github.com/apache/incubator-devlake/plugins/bamboo/models"
+       "github.com/mitchellh/mapstructure"
 )
 
+type apiProject struct {
+       models.BambooProject
+       TransformationRuleName string `json:"transformationRuleName,omitempty"`
+}
+
 type req struct {
-       Data []*models.SonarqubeProject `json:"data"`
+       Data []*models.BambooProject `json:"data"`
 }
 
-// PutScope create or update sonarqube project
-// @Summary create or update sonarqube project
-// @Description Create or update sonarqube project
-// @Tags plugins/sonarqube
+// PutScope create or update bamboo project
+// @Summary create or update bamboo project
+// @Description Create or update bamboo project
+// @Tags plugins/bamboo
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
 // @Param scope body req true "json"
-// @Success 200  {object} []models.SonarqubeProject
+// @Success 200  {object} []models.BambooProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/sonarqube/connections/{connectionId}/scopes [PUT]
+// @Router /plugins/bamboo/connections/{connectionId}/scopes [PUT]
 func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        connectionId, _ := extractParam(input.Params)
+
        if connectionId == 0 {
                return nil, errors.BadInput.New("invalid connectionId")
        }
        var projects req
-       // As we need to process *api.Iso8601Time, we need to use 
DecodeMapStruct instead of mapstructure.Decode
-       err := errors.Convert(api.DecodeMapStruct(input.Body, &projects))
+       err := errors.Convert(mapstructure.Decode(input.Body, &projects))
        if err != nil {
-               return nil, errors.BadInput.Wrap(err, "decoding Sonarqube 
project error")
+               return nil, errors.BadInput.Wrap(err, "decoding Bamboo project 
error")
        }
        keeper := make(map[string]struct{})
        for _, project := range projects.Data {
@@ -69,36 +76,36 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
        }
        err = basicRes.GetDal().CreateOrUpdate(projects.Data)
        if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
SonarqubeProject")
+               return nil, errors.Default.Wrap(err, "error on saving 
BambooProject")
        }
        return &plugin.ApiResourceOutput{Body: projects.Data, Status: 
http.StatusOK}, nil
 }
 
-// UpdateScope patch to sonarqube project
-// @Summary patch to sonarqube project
-// @Description patch to sonarqube project
-// @Tags plugins/sonarqube
+// UpdateScope patch to bamboo project
+// @Summary patch to bamboo project
+// @Description patch to bamboo project
+// @Tags plugins/bamboo
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
-// @Param projectKey path string false "project Key"
-// @Param scope body models.SonarqubeProject true "json"
-// @Success 200  {object} models.SonarqubeProject
+// @Param projectKey path int false "project ID"
+// @Param scope body models.BambooProject true "json"
+// @Success 200  {object} models.BambooProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/sonarqube/connections/{connectionId}/scopes/{projectKey} 
[PATCH]
+// @Router /plugins/bamboo/connections/{connectionId}/scopes/{projectKey} 
[PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
        connectionId, projectKey := extractParam(input.Params)
-       if connectionId*uint64(len(projectKey)) == 0 {
-               return nil, errors.BadInput.New("invalid connectionId or 
projectKey")
+       if connectionId == 0 || projectKey == "" {
+               return nil, errors.BadInput.New("invalid path params")
        }
-       var project models.SonarqubeProject
+       var project models.BambooProject
        err := basicRes.GetDal().First(&project, dal.Where("connection_id = ? 
AND project_key = ?", connectionId, projectKey))
        if err != nil {
-               return nil, errors.Default.Wrap(err, "getting SonarqubeProject 
error")
+               return nil, errors.Default.Wrap(err, "getting BambooProject 
error")
        }
        err = api.DecodeMapStruct(input.Body, &project)
        if err != nil {
-               return nil, errors.Default.Wrap(err, "patch sonarqube project 
error")
+               return nil, errors.Default.Wrap(err, "patch bamboo project 
error")
        }
        err = verifyProject(&project)
        if err != nil {
@@ -106,22 +113,22 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
        }
        err = basicRes.GetDal().Update(project)
        if err != nil {
-               return nil, errors.Default.Wrap(err, "error on saving 
SonarqubeProject")
+               return nil, errors.Default.Wrap(err, "error on saving 
BambooProject")
        }
        return &plugin.ApiResourceOutput{Body: project, Status: http.StatusOK}, 
nil
 }
 
-// GetScopeList get Sonarqube projects
-// @Summary get Sonarqube projects
-// @Description get Sonarqube projects
-// @Tags plugins/sonarqube
+// GetScopeList get Bamboo projects
+// @Summary get Bamboo projects
+// @Description get Bamboo projects
+// @Tags plugins/bamboo
 // @Param connectionId path int false "connection ID"
 // @Success 200  {object} []apiProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/sonarqube/connections/{connectionId}/scopes/ [GET]
+// @Router /plugins/bamboo/connections/{connectionId}/scopes/ [GET]
 func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var projects []models.SonarqubeProject
+       var projects []models.BambooProject
        connectionId, _ := extractParam(input.Params)
        if connectionId == 0 {
                return nil, errors.BadInput.New("invalid path params")
@@ -131,38 +138,86 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
        if err != nil {
                return nil, err
        }
-
-       return &plugin.ApiResourceOutput{Body: projects, Status: 
http.StatusOK}, nil
+       var ruleIds []uint64
+       for _, proj := range projects {
+               if proj.TransformationRuleId > 0 {
+                       ruleIds = append(ruleIds, proj.TransformationRuleId)
+               }
+       }
+       var rules []models.BambooTransformationRule
+       if len(ruleIds) > 0 {
+               err = basicRes.GetDal().All(&rules, dal.Where("id IN (?)", 
ruleIds))
+               if err != nil {
+                       return nil, err
+               }
+       }
+       names := make(map[uint64]string)
+       for _, rule := range rules {
+               names[rule.ID] = rule.Name
+       }
+       var apiProjects []apiProject
+       for _, proj := range projects {
+               apiProjects = append(apiProjects, apiProject{proj, 
names[proj.TransformationRuleId]})
+       }
+       return &plugin.ApiResourceOutput{Body: apiProjects, Status: 
http.StatusOK}, nil
 }
 
-// GetScope get one Sonarqube project
-// @Summary get one Sonarqube project
-// @Description get one Sonarqube project
-// @Tags plugins/sonarqube
+// GetScope get one Bamboo project
+// @Summary get one Bamboo project
+// @Description get one Bamboo project
+// @Tags plugins/bamboo
 // @Param connectionId path int false "connection ID"
-// @Param projectKey path string false "project key"
+// @Param projectKey path int false "project ID"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
 // @Success 200  {object} apiProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/sonarqube/connections/{connectionId}/scopes/{projectKey} 
[GET]
+// @Router /plugins/bamboo/connections/{connectionId}/scopes/{projectKey} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       var project models.SonarqubeProject
+       var project models.BambooProject
        connectionId, projectKey := extractParam(input.Params)
-       if connectionId*uint64(len(projectKey)) == 0 {
+       if connectionId == 0 || projectKey == "" {
                return nil, errors.BadInput.New("invalid path params")
        }
        db := basicRes.GetDal()
        err := db.First(&project, dal.Where("connection_id = ? AND project_key 
= ?", connectionId, projectKey))
-       if db.IsErrorNotFound(err) {
+       if err != nil && db.IsErrorNotFound(err) {
+               var scope models.BambooProject
+               connection := &models.BambooConnection{}
+               err = connectionHelper.First(connection, input.Params)
+               if err != nil {
+                       return nil, err
+               }
+               apiClient, err := 
api.NewApiClientFromConnection(context.TODO(), basicRes, connection)
+               if err != nil {
+                       return nil, err
+               }
+
+               apiProject, err := GetApiProject(projectKey, apiClient)
+               if err != nil {
+                       return nil, err
+               }
+
+               scope.Convert(apiProject)
+               scope.ConnectionId = connectionId
+               err = db.CreateIfNotExist(&scope)
+               if err != nil {
+                       return nil, err
+               }
                return nil, errors.NotFound.New("record not found")
-       }
-       if err != nil {
+       } else if err != nil {
                return nil, err
        }
 
-       return &plugin.ApiResourceOutput{Body: project, Status: http.StatusOK}, 
nil
+       var rule models.BambooTransformationRule
+       if project.TransformationRuleId > 0 {
+               err = basicRes.GetDal().First(&rule, dal.Where("id = ?", 
project.TransformationRuleId))
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return &plugin.ApiResourceOutput{Body: apiProject{project, rule.Name}, 
Status: http.StatusOK}, nil
 }
 
 func extractParam(params map[string]string) (uint64, string) {
@@ -171,12 +226,12 @@ func extractParam(params map[string]string) (uint64, 
string) {
        return connectionId, projectKey
 }
 
-func verifyProject(project *models.SonarqubeProject) errors.Error {
+func verifyProject(project *models.BambooProject) errors.Error {
        if project.ConnectionId == 0 {
                return errors.BadInput.New("invalid connectionId")
        }
-       if len(project.ProjectKey) == 0 {
-               return errors.BadInput.New("invalid project key")
+       if project.ProjectKey == "" {
+               return errors.BadInput.New("invalid projectKey")
        }
        return nil
 }
diff --git a/backend/plugins/bamboo/impl/impl.go 
b/backend/plugins/bamboo/impl/impl.go
index d8fe3b244..70cb8ede7 100644
--- a/backend/plugins/bamboo/impl/impl.go
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -19,6 +19,7 @@ package impl
 
 import (
        "fmt"
+
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
@@ -186,6 +187,20 @@ func (p Bamboo) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "PATCH":  api.PatchConnection,
                        "DELETE": api.DeleteConnection,
                },
+               "connections/:connectionId/scopes": {
+                       "GET": api.GetScopeList,
+                       "PUT": api.PutScope,
+               },
+               "connections/:connectionId/remote-scopes": {
+                       "GET": api.RemoteScopes,
+               },
+               "connections/:connectionId/search-remote-scopes": {
+                       "GET": api.SearchRemoteScopes,
+               },
+               "connections/:connectionId/scopes/:projectKey": {
+                       "GET":   api.GetScope,
+                       "PATCH": api.UpdateScope,
+               },
        }
 }
 
diff --git a/backend/plugins/bamboo/models/job.go 
b/backend/plugins/bamboo/models/job.go
index 6ee00dc08..a47a7950a 100644
--- a/backend/plugins/bamboo/models/job.go
+++ b/backend/plugins/bamboo/models/job.go
@@ -37,18 +37,15 @@ type BambooJob struct {
        common.NoPKModel
 }
 
-func (BambooJob) Convert(apiRes *ApiBambooJob) *BambooJob {
-       b := &BambooJob{
-               JobKey:      apiRes.SearchEntity.Key,
-               Id:          apiRes.Id,
-               Name:        apiRes.SearchEntity.JobName,
-               PlanName:    apiRes.SearchEntity.PlanName,
-               ProjectName: apiRes.SearchEntity.ProjectName,
-               Description: apiRes.SearchEntity.Description,
-               BranchName:  apiRes.SearchEntity.BranchName,
-               Type:        apiRes.SearchEntity.Type,
-       }
-       return b
+func (b *BambooJob) Convert(apiRes *ApiBambooJob) {
+       b.JobKey = apiRes.SearchEntity.Key
+       b.Id = apiRes.Id
+       b.Name = apiRes.SearchEntity.JobName
+       b.PlanName = apiRes.SearchEntity.PlanName
+       b.ProjectName = apiRes.SearchEntity.ProjectName
+       b.Description = apiRes.SearchEntity.Description
+       b.BranchName = apiRes.SearchEntity.BranchName
+       b.Type = apiRes.SearchEntity.Type
 }
 
 func (BambooJob) TableName() string {
diff --git 
a/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go 
b/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
index c78946a2e..bfb399c34 100644
--- a/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
+++ b/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
@@ -48,7 +48,7 @@ func (u *addInitTables) Up(baseRes context.BasicRes) 
errors.Error {
 }
 
 func (*addInitTables) Version() uint64 {
-       return 20230216205037
+       return 20230221205032
 }
 
 func (*addInitTables) Name() string {
diff --git a/backend/plugins/bamboo/models/migrationscripts/archived/project.go 
b/backend/plugins/bamboo/models/migrationscripts/archived/project.go
index 0ac9d3b8d..ccabf8ef1 100644
--- a/backend/plugins/bamboo/models/migrationscripts/archived/project.go
+++ b/backend/plugins/bamboo/models/migrationscripts/archived/project.go
@@ -22,14 +22,14 @@ import (
 )
 
 type BambooProject struct {
-       ConnectionId uint64 `gorm:"primaryKey"`
-       ProjectKey   string `gorm:"primaryKey;type:varchar(100)"`
-       Expand       string `json:"expand"`
-       Name         string `gorm:"index;type:varchar(100)"`
-       Description  string `json:"description"`
-       Href         string `json:"link"`
-       Rel          string `gorm:"type:varchar(100)"`
-       archived.NoPKModel
+       ConnectionId         uint64 `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey"`
+       ProjectKey           string `json:"projectKey" 
gorm:"primaryKey;type:varchar(256)"`
+       TransformationRuleId uint64 `json:"transformationRuleId,omitempty" 
mapstructure:"transformationRuleId"`
+       Name                 string `json:"name" gorm:"index;type:varchar(256)"`
+       Description          string `json:"description"`
+       Href                 string `json:"link"`
+       Rel                  string `json:"rel" gorm:"type:varchar(100)"`
+       archived.NoPKModel   `json:"-" mapstructure:"-"`
 }
 
 func (BambooProject) TableName() string {
diff --git a/backend/plugins/bamboo/models/plan.go 
b/backend/plugins/bamboo/models/plan.go
index 4720f8842..8b35ae4f5 100644
--- a/backend/plugins/bamboo/models/plan.go
+++ b/backend/plugins/bamboo/models/plan.go
@@ -43,27 +43,24 @@ type BambooPlan struct {
        common.NoPKModel
 }
 
-func (BambooPlan) Convert(apiProject *ApiBambooPlan) *BambooPlan {
-       b := &BambooPlan{
-               PlanKey:                   apiProject.Key,
-               Name:                      apiProject.Name,
-               Expand:                    apiProject.Expand,
-               ProjectKey:                apiProject.ProjectKey,
-               ProjectName:               apiProject.ProjectName,
-               Description:               apiProject.Description,
-               ShortName:                 apiProject.ShortName,
-               BuildName:                 apiProject.BuildName,
-               ShortKey:                  apiProject.ShortKey,
-               Type:                      apiProject.Type,
-               Enabled:                   apiProject.Enabled,
-               Href:                      apiProject.Href,
-               Rel:                       apiProject.Rel,
-               IsFavourite:               apiProject.IsFavourite,
-               IsActive:                  apiProject.IsActive,
-               IsBuilding:                apiProject.IsBuilding,
-               AverageBuildTimeInSeconds: apiProject.AverageBuildTimeInSeconds,
-       }
-       return b
+func (b *BambooPlan) Convert(apiProject *ApiBambooPlan) {
+       b.PlanKey = apiProject.Key
+       b.Name = apiProject.Name
+       b.Expand = apiProject.Expand
+       b.ProjectKey = apiProject.ProjectKey
+       b.ProjectName = apiProject.ProjectName
+       b.Description = apiProject.Description
+       b.ShortName = apiProject.ShortName
+       b.BuildName = apiProject.BuildName
+       b.ShortKey = apiProject.ShortKey
+       b.Type = apiProject.Type
+       b.Enabled = apiProject.Enabled
+       b.Href = apiProject.Href
+       b.Rel = apiProject.Rel
+       b.IsFavourite = apiProject.IsFavourite
+       b.IsActive = apiProject.IsActive
+       b.IsBuilding = apiProject.IsBuilding
+       b.AverageBuildTimeInSeconds = apiProject.AverageBuildTimeInSeconds
 }
 
 func (BambooPlan) TableName() string {
diff --git a/backend/plugins/bamboo/models/project.go 
b/backend/plugins/bamboo/models/project.go
index d8b903351..89e7caa4b 100644
--- a/backend/plugins/bamboo/models/project.go
+++ b/backend/plugins/bamboo/models/project.go
@@ -19,6 +19,7 @@ package models
 
 import (
        "encoding/json"
+
        "github.com/apache/incubator-devlake/core/models/common"
 )
 
@@ -70,3 +71,21 @@ type ApiBambooProjectResponse struct {
        Link     ApiBambooLink     `json:"link"`
        Projects ApiBambooProjects `json:"projects"`
 }
+
+type ApiSearchEntityProject struct {
+       Id          string `json:"id"`
+       Key         string `json:"key"`
+       ProjectName string `json:"projectName"`
+       Description string `json:"description"`
+}
+
+type ApiSearchResultProjects struct {
+       Id           string                 `json:"id"`
+       Type         string                 `json:"type"`
+       SearchEntity ApiSearchEntityProject `json:"searchResults"`
+}
+
+type ApiBambooSearchProjectResponse struct {
+       ApiBambooSizeData `json:"squash"`
+       SearchResults     []ApiSearchResultProjects `json:"searchResults"`
+}
diff --git a/backend/plugins/bamboo/tasks/job_extractor.go 
b/backend/plugins/bamboo/tasks/job_extractor.go
index 74515fcbf..5668eabeb 100644
--- a/backend/plugins/bamboo/tasks/job_extractor.go
+++ b/backend/plugins/bamboo/tasks/job_extractor.go
@@ -19,6 +19,7 @@ package tasks
 
 import (
        "encoding/json"
+
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
@@ -44,7 +45,8 @@ func ExtractJob(taskCtx plugin.SubTaskContext) errors.Error {
                        if err != nil {
                                return nil, err
                        }
-                       body := models.BambooJob{}.Convert(res)
+                       body := &models.BambooJob{}
+                       body.Convert(res)
                        body.ConnectionId = data.Options.ConnectionId
                        body.ProjectKey = data.Options.ProjectKey
                        body.PlanKey = plan.PlanKey
diff --git a/backend/plugins/bamboo/tasks/plan_extractor.go 
b/backend/plugins/bamboo/tasks/plan_extractor.go
index 5174b7b5e..43d92e0f1 100644
--- a/backend/plugins/bamboo/tasks/plan_extractor.go
+++ b/backend/plugins/bamboo/tasks/plan_extractor.go
@@ -19,6 +19,7 @@ package tasks
 
 import (
        "encoding/json"
+
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
@@ -39,7 +40,8 @@ func ExtractPlan(taskCtx plugin.SubTaskContext) errors.Error {
                        if err != nil {
                                return nil, err
                        }
-                       body := models.BambooPlan{}.Convert(res)
+                       body := &models.BambooPlan{}
+                       body.Convert(res)
                        body.ConnectionId = data.Options.ConnectionId
                        return []interface{}{body}, nil
                },
diff --git a/backend/plugins/sonarqube/api/scope.go 
b/backend/plugins/sonarqube/api/scope.go
index 063020133..3ec71af35 100644
--- a/backend/plugins/sonarqube/api/scope.go
+++ b/backend/plugins/sonarqube/api/scope.go
@@ -116,7 +116,7 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Description get Sonarqube projects
 // @Tags plugins/sonarqube
 // @Param connectionId path int false "connection ID"
-// @Success 200  {object} []apiProject
+// @Success 200  {object} []models.SonarqubeProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/sonarqube/connections/{connectionId}/scopes/ [GET]
@@ -143,7 +143,7 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Param projectKey path string false "project key"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
-// @Success 200  {object} apiProject
+// @Success 200  {object} models.SonarqubeProject
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/sonarqube/connections/{connectionId}/scopes/{projectKey} 
[GET]

Reply via email to