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

likyh 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 f04a51267 refactor(sonarqube): refactor search scopes (#4707)
f04a51267 is described below

commit f04a5126747ee308bab69e0a6226ee30108b7eea
Author: Warren Chen <[email protected]>
AuthorDate: Tue Mar 21 10:46:47 2023 +0800

    refactor(sonarqube): refactor search scopes (#4707)
---
 backend/plugins/sonarqube/api/blueprint_v200.go    |   5 +-
 backend/plugins/sonarqube/api/init.go              |   6 +
 backend/plugins/sonarqube/api/remote.go            | 306 ++++-----------------
 backend/plugins/sonarqube/impl/impl.go             |   6 +-
 .../plugins/sonarqube/models/sonarqube_project.go  |  34 +++
 backend/plugins/sonarqube/tasks/shared.go          |  22 --
 6 files changed, 104 insertions(+), 275 deletions(-)

diff --git a/backend/plugins/sonarqube/api/blueprint_v200.go 
b/backend/plugins/sonarqube/api/blueprint_v200.go
index dd6f9f99c..a60e50324 100644
--- a/backend/plugins/sonarqube/api/blueprint_v200.go
+++ b/backend/plugins/sonarqube/api/blueprint_v200.go
@@ -32,7 +32,6 @@ import (
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        aha 
"github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
        "github.com/apache/incubator-devlake/plugins/sonarqube/models"
-       "github.com/apache/incubator-devlake/plugins/sonarqube/tasks"
 )
 
 func MakeDataSourcePipelinePlanV200(subtaskMetas []plugin.SubTaskMeta, 
connectionId uint64, bpScopes []*plugin.BlueprintScopeV200, syncPolicy 
*plugin.BlueprintSyncPolicy) (plugin.PipelinePlan, []plugin.Scope, 
errors.Error) {
@@ -109,9 +108,9 @@ func makeScopesV200(bpScopes []*plugin.BlueprintScopeV200, 
connectionId uint64)
 func GetApiProject(
        projectKey string,
        apiClient aha.ApiClientAbstract,
-) (*tasks.SonarqubeApiProject, errors.Error) {
+) (*models.SonarqubeApiProject, errors.Error) {
        var resData struct {
-               Data []tasks.SonarqubeApiProject `json:"components"`
+               Data []models.SonarqubeApiProject `json:"components"`
        }
        query := url.Values{}
        query.Set("q", projectKey)
diff --git a/backend/plugins/sonarqube/api/init.go 
b/backend/plugins/sonarqube/api/init.go
index 54c9c2bff..37bf1fcfb 100644
--- a/backend/plugins/sonarqube/api/init.go
+++ b/backend/plugins/sonarqube/api/init.go
@@ -27,6 +27,7 @@ import (
 var vld *validator.Validate
 var connectionHelper *api.ConnectionApiHelper
 var scopeHelper *api.ScopeApiHelper[models.SonarqubeConnection, 
models.SonarqubeProject, interface{}]
+var remoteHelper *api.RemoteApiHelper[models.SonarqubeConnection, 
models.SonarqubeProject, models.SonarqubeApiProject, api.NoRemoteGroupResponse]
 var basicRes context.BasicRes
 
 func Init(br context.BasicRes) {
@@ -41,4 +42,9 @@ func Init(br context.BasicRes) {
                vld,
                connectionHelper,
        )
+       remoteHelper = api.NewRemoteHelper[models.SonarqubeConnection, 
models.SonarqubeProject, models.SonarqubeApiProject, api.NoRemoteGroupResponse](
+               basicRes,
+               vld,
+               connectionHelper,
+       )
 }
diff --git a/backend/plugins/sonarqube/api/remote.go 
b/backend/plugins/sonarqube/api/remote.go
index ba5e18c08..8f0be7030 100644
--- a/backend/plugins/sonarqube/api/remote.go
+++ b/backend/plugins/sonarqube/api/remote.go
@@ -19,48 +19,15 @@ package api
 
 import (
        "context"
-       "encoding/base64"
-       "encoding/json"
        "fmt"
-       "net/http"
-       "net/url"
-       "strconv"
-
+       context2 "github.com/apache/incubator-devlake/core/context"
        "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/sonarqube/tasks"
+       "net/url"
 )
 
-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"`
-       PerPage int    `json:"per_page"`
-       Tag     string `json:"tag"`
-}
-
-const SonarqubeRemoteScopesPerPage int = 100
-const TypeProject string = "scope"
-
 // RemoteScopes list all available scope for users
 // @Summary list all available scope for users
 // @Description list all available scope for users
@@ -68,90 +35,36 @@ const TypeProject string = "scope"
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
 // @Param pageToken query string false "page Token"
-// @Success 200  {object} RemoteScopesOutput
+// @Success 200  {object} api.RemoteScopesOutput
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/sonarqube/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.SonarqubeConnection{}
-       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("fail to get page token")
-       }
-
-       // create api client
-       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, connection)
-       if err != nil {
-               return nil, err
-       }
-
-       var res *http.Response
-       outputBody := &RemoteScopesOutput{}
-
-       // list projects part
-       query, err := GetQueryFromPageData(pageData)
-       if err != nil {
-               return nil, err
-       }
-       res, err = apiClient.Get("projects/search", query, nil)
-
-       if err != nil {
-               return nil, err
-       }
-
-       var resBody struct {
-               Data []tasks.SonarqubeApiProject `json:"components"`
-       }
-       err = api.UnmarshalResponse(res, &resBody)
-
-       // append project to output
-       for _, project := range resBody.Data {
-               child := RemoteScopesChild{
-                       Type: TypeProject,
-                       Id:   project.ProjectKey,
-                       Name: project.Name,
-                       Data: tasks.ConvertProject(&project),
-               }
-               outputBody.Children = append(outputBody.Children, child)
-       }
-
-       // check project count
-       if err != nil {
-               return nil, err
-       }
-       if len(resBody.Data) < pageData.PerPage {
-               pageData = nil
-       }
-
-       // get the next page token
-       outputBody.NextPageToken = ""
-       if pageData != nil {
-               pageData.Page += 1
-               pageData.PerPage = SonarqubeRemoteScopesPerPage
-
-               outputBody.NextPageToken, err = 
GetPageTokenFromPageData(pageData)
-               if err != nil {
-                       return nil, err
-               }
-       }
-
-       return &plugin.ApiResourceOutput{Body: outputBody, Status: 
http.StatusOK}, nil
+       return remoteHelper.GetScopesFromRemote(input,
+               nil,
+               func(basicRes context2.BasicRes, gid string, queryData 
*plugin.QueryData, connection models.SonarqubeConnection) 
([]models.SonarqubeApiProject, errors.Error) {
+                       query := initialQuery(queryData)
+                       // create api client
+                       apiClient, err := 
api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       res, err := apiClient.Get("projects/search", query, nil)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       var resBody struct {
+                               Data []models.SonarqubeApiProject 
`json:"components"`
+                       }
+                       err = api.UnmarshalResponse(res, &resBody)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       return resBody.Data, nil
+               })
 }
 
 // SearchRemoteScopes use the Search API and only return project
@@ -162,143 +75,42 @@ func RemoteScopes(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Param connectionId path int false "connection ID"
 // @Param page query int false "page number"
 // @Param pageSize query int false "page size per page"
-// @Success 200  {object} SearchRemoteScopesOutput
+// @Success 200  {object} api.SearchRemoteScopesOutput
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/sonarqube/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.SonarqubeConnection{}
-       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 = SonarqubeRemoteScopesPerPage
-       } 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, err := GetQueryForSearchProject(search[0], p, ps)
-       if err != nil {
-               return nil, err
-       }
-
-       // request search
-       res, err := apiClient.Get("projects/search", query, nil)
-       if err != nil {
-               return nil, err
-       }
-       var resBody struct {
-               Data []tasks.SonarqubeApiProject `json:"components"`
-       }
-       err = api.UnmarshalResponse(res, &resBody)
-       if err != nil {
-               return nil, err
-       }
-
-       // set projects return
-       outputBody := &SearchRemoteScopesOutput{}
-       for _, project := range resBody.Data {
-               child := RemoteScopesChild{
-                       Type:     TypeProject,
-                       Id:       project.ProjectKey,
-                       ParentId: nil,
-                       Name:     project.Name,
-                       Data:     tasks.ConvertProject(&project),
-               }
-               outputBody.Children = append(outputBody.Children, child)
-       }
-       outputBody.Page = p
-       outputBody.PageSize = ps
-
-       return &plugin.ApiResourceOutput{Body: outputBody, Status: 
http.StatusOK}, nil
+       return remoteHelper.GetScopesFromRemote(input,
+               nil,
+               func(basicRes context2.BasicRes, gid string, queryData 
*plugin.QueryData, connection models.SonarqubeConnection) 
([]models.SonarqubeApiProject, errors.Error) {
+                       query := initialQuery(queryData)
+                       query.Set("q", queryData.Search[0])
+                       // create api client
+                       apiClient, err := 
api.NewApiClientFromConnection(context.TODO(), basicRes, &connection)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       // request search
+                       res, err := apiClient.Get("projects/search", query, nil)
+                       if err != nil {
+                               return nil, err
+                       }
+                       var resBody struct {
+                               Data []models.SonarqubeApiProject 
`json:"components"`
+                       }
+                       err = api.UnmarshalResponse(res, &resBody)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       return resBody.Data, 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,
-                       PerPage: SonarqubeRemoteScopesPerPage,
-               }, 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, errors.Error) {
+func initialQuery(queryData *plugin.QueryData) url.Values {
        query := url.Values{}
-       query.Set("p", fmt.Sprintf("%v", pageData.Page))
-       query.Set("ps", fmt.Sprintf("%v", pageData.PerPage))
-       return query, nil
-}
-
-func GetQueryForSearchProject(search string, page int, perPage int) 
(url.Values, errors.Error) {
-       query, err := GetQueryFromPageData(&PageData{Page: page, PerPage: 
perPage})
-       if err != nil {
-               return nil, err
-       }
-       query.Set("q", search)
-       return query, nil
-}
-
-func extractParam(params map[string]string) (uint64, string) {
-       connectionId, _ := strconv.ParseUint(params["connectionId"], 10, 64)
-       projectKey := params["projectKey"]
-       return connectionId, projectKey
+       query.Set("p", fmt.Sprintf("%v", queryData.Page))
+       query.Set("ps", fmt.Sprintf("%v", queryData.PerPage))
+       return query
 }
diff --git a/backend/plugins/sonarqube/impl/impl.go 
b/backend/plugins/sonarqube/impl/impl.go
index 779cffc68..92359d6dd 100644
--- a/backend/plugins/sonarqube/impl/impl.go
+++ b/backend/plugins/sonarqube/impl/impl.go
@@ -125,14 +125,14 @@ func (p Sonarqube) PrepareTaskData(taskCtx 
plugin.TaskContext, options map[strin
                ApiClient: apiClient,
        }
        // even we have project in _tool_sonaqube_projects, we still need to 
collect project to update LastAnalysisDate
-       var scope *models.SonarqubeProject
-       var apiProject *tasks.SonarqubeApiProject
+       var scope models.SonarqubeProject
+       var apiProject *models.SonarqubeApiProject
        apiProject, err = api.GetApiProject(op.ProjectKey, apiClient)
        if err != nil {
                return nil, err
        }
        logger.Debug(fmt.Sprintf("Current project: %s", apiProject.ProjectKey))
-       scope = tasks.ConvertProject(apiProject)
+       scope = apiProject.ConvertApiScope().(models.SonarqubeProject)
        scope.ConnectionId = op.ConnectionId
        err = taskCtx.GetDal().CreateOrUpdate(&scope)
        if err != nil {
diff --git a/backend/plugins/sonarqube/models/sonarqube_project.go 
b/backend/plugins/sonarqube/models/sonarqube_project.go
index 47fc6037d..b590df1f1 100644
--- a/backend/plugins/sonarqube/models/sonarqube_project.go
+++ b/backend/plugins/sonarqube/models/sonarqube_project.go
@@ -19,9 +19,13 @@ package models
 
 import (
        "github.com/apache/incubator-devlake/core/models/common"
+       "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 )
 
+var _ plugin.ToolLayerScope = (*SonarqubeProject)(nil)
+var _ plugin.ApiScope = (*SonarqubeApiProject)(nil)
+
 type SonarqubeProject struct {
        common.NoPKModel `json:"-" mapstructure:"-"`
        ConnectionId     uint64           `json:"connectionId" 
validate:"required" gorm:"primaryKey"`
@@ -36,3 +40,33 @@ type SonarqubeProject struct {
 func (SonarqubeProject) TableName() string {
        return "_tool_sonarqube_projects"
 }
+
+func (p SonarqubeProject) ScopeId() string {
+       return p.ProjectKey
+}
+
+func (p SonarqubeProject) ScopeName() string {
+       return p.Name
+}
+
+type SonarqubeApiProject struct {
+       ProjectKey       string           `json:"key"`
+       Name             string           `json:"name"`
+       Qualifier        string           `json:"qualifier"`
+       Visibility       string           `json:"visibility"`
+       LastAnalysisDate *api.Iso8601Time `json:"lastAnalysisDate"`
+       Revision         string           `json:"revision"`
+}
+
+// Convert the API response to our DB model instance
+func (sonarqubeApiProject SonarqubeApiProject) ConvertApiScope() 
plugin.ToolLayerScope {
+       sonarqubeProject := SonarqubeProject{
+               ProjectKey:       sonarqubeApiProject.ProjectKey,
+               Name:             sonarqubeApiProject.Name,
+               Qualifier:        sonarqubeApiProject.Qualifier,
+               Visibility:       sonarqubeApiProject.Visibility,
+               LastAnalysisDate: sonarqubeApiProject.LastAnalysisDate,
+               Revision:         sonarqubeApiProject.Revision,
+       }
+       return sonarqubeProject
+}
diff --git a/backend/plugins/sonarqube/tasks/shared.go 
b/backend/plugins/sonarqube/tasks/shared.go
index c30eba684..bd4ced97c 100644
--- a/backend/plugins/sonarqube/tasks/shared.go
+++ b/backend/plugins/sonarqube/tasks/shared.go
@@ -72,28 +72,6 @@ type Paging struct {
        Total     int `json:"total"`
 }
 
-type SonarqubeApiProject struct {
-       ProjectKey       string           `json:"key"`
-       Name             string           `json:"name"`
-       Qualifier        string           `json:"qualifier"`
-       Visibility       string           `json:"visibility"`
-       LastAnalysisDate *api.Iso8601Time `json:"lastAnalysisDate"`
-       Revision         string           `json:"revision"`
-}
-
-// Convert the API response to our DB model instance
-func ConvertProject(sonarqubeApiProject *SonarqubeApiProject) 
*models.SonarqubeProject {
-       sonarqubeProject := &models.SonarqubeProject{
-               ProjectKey:       sonarqubeApiProject.ProjectKey,
-               Name:             sonarqubeApiProject.Name,
-               Qualifier:        sonarqubeApiProject.Qualifier,
-               Visibility:       sonarqubeApiProject.Visibility,
-               LastAnalysisDate: sonarqubeApiProject.LastAnalysisDate,
-               Revision:         sonarqubeApiProject.Revision,
-       }
-       return sonarqubeProject
-}
-
 func generateId(hashCodeBlock hash.Hash, entity 
*models.SonarqubeIssueCodeBlock) {
        hashCodeBlock.Write([]byte(fmt.Sprintf("%s-%s-%d-%d-%d-%d-%s", 
entity.IssueKey, entity.Component, entity.StartLine, entity.EndLine, 
entity.StartOffset, entity.EndOffset, entity.Msg)))
        entity.Id = hex.EncodeToString(hashCodeBlock.Sum(nil))

Reply via email to