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

abeizn 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 5b7e0da90 feat(sonarqube): add bp v100 support (#4469)
5b7e0da90 is described below

commit 5b7e0da900936bfcb1907d4a30dc9cc58e8ce8ae
Author: Warren Chen <[email protected]>
AuthorDate: Tue Feb 21 20:59:02 2023 +0800

    feat(sonarqube): add bp v100 support (#4469)
---
 backend/plugins/sonarqube/api/blueprint_v200.go    | 30 +++++++
 backend/plugins/sonarqube/e2e/project_test.go      | 25 +-----
 .../e2e/raw_tables/_raw_sonarqube_projects.csv     | 12 ---
 .../_tool_sonarqube_projects.csv                   |  0
 backend/plugins/sonarqube/impl/impl.go             | 26 ++++++-
 .../sonarqube/tasks/filemetrics_convertor.go       |  2 +
 .../plugins/sonarqube/tasks/projects_collector.go  | 82 -------------------
 .../plugins/sonarqube/tasks/projects_extractor.go  | 91 ----------------------
 backend/plugins/sonarqube/tasks/shared.go          | 23 ++++++
 9 files changed, 83 insertions(+), 208 deletions(-)

diff --git a/backend/plugins/sonarqube/api/blueprint_v200.go 
b/backend/plugins/sonarqube/api/blueprint_v200.go
index d2920f8bc..611cec815 100644
--- a/backend/plugins/sonarqube/api/blueprint_v200.go
+++ b/backend/plugins/sonarqube/api/blueprint_v200.go
@@ -27,7 +27,11 @@ import (
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/core/utils"
        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"
+       "net/http"
+       "net/url"
        "time"
 )
 
@@ -104,3 +108,29 @@ func makeScopesV200(bpScopes []*plugin.BlueprintScopeV200, 
connectionId uint64)
        }
        return scopes, nil
 }
+
+func GetApiProject(
+       projectKey string,
+       apiClient aha.ApiClientAbstract,
+) (*tasks.SonarqubeApiProject, errors.Error) {
+       var resData struct {
+               Data []tasks.SonarqubeApiProject `json:"components"`
+       }
+       query := url.Values{}
+       query.Set("q", projectKey)
+       res, err := apiClient.Get("projects/search", query, nil)
+       if err != nil {
+               return nil, err
+       }
+       if res.StatusCode != http.StatusOK {
+               return nil, 
errors.HttpStatus(res.StatusCode).New(fmt.Sprintf("unexpected status code when 
requesting project detail from %s", res.Request.URL.String()))
+       }
+       err = helper.UnmarshalResponse(res, &resData)
+       if err != nil {
+               return nil, err
+       }
+       if len(resData.Data) > 0 {
+               return &resData.Data[0], nil
+       }
+       return nil, errors.BadInput.New(fmt.Sprintf("Cannot find project: %s", 
projectKey))
+}
diff --git a/backend/plugins/sonarqube/e2e/project_test.go 
b/backend/plugins/sonarqube/e2e/project_test.go
index aeea1adec..930b9a835 100644
--- a/backend/plugins/sonarqube/e2e/project_test.go
+++ b/backend/plugins/sonarqube/e2e/project_test.go
@@ -33,36 +33,19 @@ func TestSonarqubeProjectDataFlow(t *testing.T) {
        var sonarqube impl.Sonarqube
        dataflowTester := e2ehelper.NewDataFlowTester(t, "sonarqube", sonarqube)
 
-       taskData := &tasks.SonarqubeTaskData{
-               Options: &tasks.SonarqubeOptions{
-                       ConnectionId: 1,
-                       ProjectKey:   "fa2cf9cd-c448-4fc3-99a5-1c893f15d84c",
-               },
-       }
-
        // import raw data table
-       
dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_sonarqube_projects.csv",
-               "_raw_sonarqube_projects")
+       
dataflowTester.ImportCsvIntoTabler("./raw_tables/_tool_sonarqube_projects.csv",
+               &models.SonarqubeProject{})
 
-       // verify extraction
-       dataflowTester.FlushTabler(&models.SonarqubeProject{})
-       dataflowTester.Subtask(tasks.ExtractProjectsMeta, taskData)
-
-       taskData2 := &tasks.SonarqubeTaskData{
+       taskData := &tasks.SonarqubeTaskData{
                Options: &tasks.SonarqubeOptions{
                        ConnectionId: 2,
                        ProjectKey:   "e2c6d5e9-a321-4e8c-b322-03d9599ef962",
                },
        }
 
-       dataflowTester.Subtask(tasks.ExtractProjectsMeta, taskData2)
-       dataflowTester.VerifyTableWithOptions(&models.SonarqubeProject{}, 
e2ehelper.TableOptions{
-               CSVRelPath:  "./snapshot_tables/_tool_sonarqube_projects.csv",
-               IgnoreTypes: []interface{}{common.NoPKModel{}},
-       })
-
        dataflowTester.FlushTabler(&codequality.CqProject{})
-       dataflowTester.Subtask(tasks.ConvertProjectsMeta, taskData2)
+       dataflowTester.Subtask(tasks.ConvertProjectsMeta, taskData)
        dataflowTester.VerifyTableWithOptions(&codequality.CqProject{}, 
e2ehelper.TableOptions{
                CSVRelPath:  "./snapshot_tables/projects.csv",
                IgnoreTypes: []interface{}{common.NoPKModel{}},
diff --git 
a/backend/plugins/sonarqube/e2e/raw_tables/_raw_sonarqube_projects.csv 
b/backend/plugins/sonarqube/e2e/raw_tables/_raw_sonarqube_projects.csv
deleted file mode 100644
index a4da44afd..000000000
--- a/backend/plugins/sonarqube/e2e/raw_tables/_raw_sonarqube_projects.csv
+++ /dev/null
@@ -1,12 +0,0 @@
-"id","params","data","url","input","created_at"
-642,"{""connectionId"":1,""ProjectKey"":""fa2cf9cd-c448-4fc3-99a5-1c893f15d84c""}","{""key"":""fa2cf9cd-c448-4fc3-99a5-1c893f15d84c"",""name"":""activeadmin"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2022-12-23T13:52:39+0000"",""revision"":""51689e48ec3faa51127b8462b2219e5e0c09ad8a""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-643,"{""connectionId"":1,""ProjectKey"":""""}","{""key"":""a67b8166-5768-46a5-8830-6d86d797419a"",""name"":""actix-web"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2023-01-04T22:36:46+0000"",""revision"":""08c2cdf6416287d63387584213ea3dd41b007ef6""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-644,"{""connectionId"":2,""ProjectKey"":""""}","{""key"":""a7c268ed-525b-438d-8729-18e83229e66c"",""name"":""Aerial"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2022-12-22T18:26:29+0000"",""revision"":""405bc2494af895f8b9ccbf12f3d0c9530a154011""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-645,"{""connectionId"":2,""ProjectKey"":""""}","{""key"":""f5a50c63-2e8f-4107-9014-853f6f467757"",""name"":""aerosolve"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2023-01-02T06:58:08+0000"",""revision"":""442e76ebb7cbc7f60b04fbfac30dbf862aaffc67""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-646,"{""connectionId"":2,""ProjectKey"":""""}","{""key"":""f960efd5-84eb-44c9-888c-996457ed76d7"",""name"":""AFNetworking"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2022-12-22T12:28:19+0000"",""revision"":""4eaec5b586ddd897ebeda896e332a62a9fdab818""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-647,"{""connectionId"":2,""ProjectKey"":""""}","{""key"":""8b075027-0a3f-44ff-89e1-751d173b4f9d"",""name"":""akka"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2022-12-20T16:27:23+0000"",""revision"":""b7b79a2da1231520fe66532b7af53179d1714952""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-648,"{""connectionId"":3,""ProjectKey"":""""}","{""key"":""eb4e9de1-85f1-4568-9352-94a264ba5bef"",""name"":""akka"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2023-01-01T03:54:58+0000"",""revision"":""50372b37c5cf9ef467c4637f4e526a5fe95fc9c1""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-649,"{""connectionId"":3,""ProjectKey"":""""}","{""key"":""4f3b6f44-dbcd-4507-9cc8-c3e460f1c69e"",""name"":""Alamofire"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2023-01-05T01:14:55+0000"",""revision"":""ce7f662709a152e840b130a158b9afef3402e146""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-650,"{""connectionId"":3,""ProjectKey"":""f1f70ac0-e607-42b8-8fda-46b28f7d3430""}","{""key"":""f1f70ac0-e607-42b8-8fda-46b28f7d3430"",""name"":""algorithms"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2022-12-26T10:57:53+0000"",""revision"":""52b9408c98eaa8458083b27410c0c364406d6e24""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-651,"{""connectionId"":2,""ProjectKey"":""e2c6d5e9-a321-4e8c-b322-03d9599ef962""}","{""key"":""e2c6d5e9-a321-4e8c-b322-03d9599ef962"",""name"":""Android-Universal-Image-Loader"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2022-12-24T18:42:09+0000"",""revision"":""458df4da2e23ba9ad76c79241a948cdfcccf72ae""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
-652,"{""connectionId"":1,""ProjectKey"":""""}","{""key"":""0892ae2f-6a07-4613-a1dc-bf29158afc52"",""name"":""AndroidUtilCode"",""qualifier"":""TRK"",""visibility"":""public"",""lastAnalysisDate"":""2023-01-05T01:25:34+0000"",""revision"":""51c8124045ec5b7c6d342b94a4020ed2f7fda496""}","http://35.89.154.221:30008/api/projects/search?p=1&ps=100","null","2023-02-10
 02:46:14.044"
diff --git 
a/backend/plugins/sonarqube/e2e/snapshot_tables/_tool_sonarqube_projects.csv 
b/backend/plugins/sonarqube/e2e/raw_tables/_tool_sonarqube_projects.csv
similarity index 100%
rename from 
backend/plugins/sonarqube/e2e/snapshot_tables/_tool_sonarqube_projects.csv
rename to backend/plugins/sonarqube/e2e/raw_tables/_tool_sonarqube_projects.csv
diff --git a/backend/plugins/sonarqube/impl/impl.go 
b/backend/plugins/sonarqube/impl/impl.go
index 653fcb420..e4655274d 100644
--- a/backend/plugins/sonarqube/impl/impl.go
+++ b/backend/plugins/sonarqube/impl/impl.go
@@ -82,8 +82,6 @@ func (p Sonarqube) GetTablesInfo() []dal.Tabler {
 
 func (p Sonarqube) SubTaskMetas() []plugin.SubTaskMeta {
        return []plugin.SubTaskMeta{
-               tasks.CollectProjectsMeta,
-               tasks.ExtractProjectsMeta,
                tasks.CollectIssuesMeta,
                tasks.ExtractIssuesMeta,
                tasks.CollectHotspotsMeta,
@@ -125,6 +123,30 @@ func (p Sonarqube) PrepareTaskData(taskCtx 
plugin.TaskContext, options map[strin
                Options:   op,
                ApiClient: apiClient,
        }
+       if op.ProjectKey != "" {
+               var scope *models.SonarqubeProject
+               var apiProject *tasks.SonarqubeApiProject
+               // support v100 & advance mode
+               // If we still cannot find the record in db, we have to request 
from remote server and save it to db
+               db := taskCtx.GetDal()
+               err = db.First(&scope, dal.Where("connection_id = ? AND 
project_key = ?", op.ConnectionId, op.ProjectKey))
+               if err != nil && db.IsErrorNotFound(err) {
+                       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.ConnectionId = op.ConnectionId
+                       err = taskCtx.GetDal().CreateIfNotExist(&scope)
+                       if err != nil {
+                               return nil, err
+                       }
+               }
+               if err != nil {
+                       return nil, errors.Default.Wrap(err, fmt.Sprintf("fail 
to find project: %s", op.ProjectKey))
+               }
+       }
        var createdDateAfter time.Time
        if op.CreatedDateAfter != "" {
                createdDateAfter, err = 
errors.Convert01(time.Parse(time.RFC3339, op.CreatedDateAfter))
diff --git a/backend/plugins/sonarqube/tasks/filemetrics_convertor.go 
b/backend/plugins/sonarqube/tasks/filemetrics_convertor.go
index 4440d3208..2cc7d911d 100644
--- a/backend/plugins/sonarqube/tasks/filemetrics_convertor.go
+++ b/backend/plugins/sonarqube/tasks/filemetrics_convertor.go
@@ -29,6 +29,8 @@ import (
        "reflect"
 )
 
+const RAW_PROJECTS_TABLE = "sonarqube_projects"
+
 var ConvertFileMetricsMeta = plugin.SubTaskMeta{
        Name:             "convertFileMetrics",
        EntryPoint:       ConvertFileMetrics,
diff --git a/backend/plugins/sonarqube/tasks/projects_collector.go 
b/backend/plugins/sonarqube/tasks/projects_collector.go
deleted file mode 100644
index ba57b81bf..000000000
--- a/backend/plugins/sonarqube/tasks/projects_collector.go
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
-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 tasks
-
-import (
-       "encoding/json"
-       "fmt"
-       "github.com/apache/incubator-devlake/core/errors"
-       "github.com/apache/incubator-devlake/core/plugin"
-       helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
-       "net/http"
-       "net/url"
-)
-
-const RAW_PROJECTS_TABLE = "sonarqube_projects"
-
-var _ plugin.SubTaskEntryPoint = CollectProjects
-
-func CollectProjects(taskCtx plugin.SubTaskContext) errors.Error {
-       rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, 
RAW_PROJECTS_TABLE)
-       logger := taskCtx.GetLogger()
-       logger.Info("collect projects")
-
-       collectorWithState, err := 
helper.NewApiCollectorWithState(*rawDataSubTaskArgs, data.CreatedDateAfter)
-       if err != nil {
-               return err
-       }
-       err = collectorWithState.InitCollector(helper.ApiCollectorArgs{
-               ApiClient:   data.ApiClient,
-               PageSize:    100,
-               UrlTemplate: "projects/search",
-               Query: func(reqData *helper.RequestData) (url.Values, 
errors.Error) {
-                       query := url.Values{}
-                       if data.CreatedDateAfter != nil {
-                               query.Set("analyzedBefore",
-                                       
data.CreatedDateAfter.Format("2006-01-02"))
-                       }
-                       if data.Options.ProjectKey != "" {
-                               query.Set("q", data.Options.ProjectKey)
-                       }
-                       query.Set("p", fmt.Sprintf("%v", reqData.Pager.Page))
-                       query.Set("ps", fmt.Sprintf("%v", reqData.Pager.Size))
-                       return query, nil
-               },
-               GetTotalPages: GetTotalPagesFromResponse,
-               ResponseParser: func(res *http.Response) ([]json.RawMessage, 
errors.Error) {
-                       var resData struct {
-                               Data []json.RawMessage `json:"components"`
-                       }
-                       err = helper.UnmarshalResponse(res, &resData)
-                       return resData.Data, err
-               },
-       })
-       if err != nil {
-               return err
-       }
-
-       return collectorWithState.Execute()
-}
-
-var CollectProjectsMeta = plugin.SubTaskMeta{
-       Name:             "CollectProjects",
-       EntryPoint:       CollectProjects,
-       EnabledByDefault: true,
-       Description:      "Collect Projects data from Sonarqube api",
-       DomainTypes:      []string{plugin.DOMAIN_TYPE_CODE_QUALITY},
-}
diff --git a/backend/plugins/sonarqube/tasks/projects_extractor.go 
b/backend/plugins/sonarqube/tasks/projects_extractor.go
deleted file mode 100644
index aaf28f13d..000000000
--- a/backend/plugins/sonarqube/tasks/projects_extractor.go
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
-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 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"
-       "github.com/apache/incubator-devlake/plugins/sonarqube/models"
-)
-
-var _ plugin.SubTaskEntryPoint = ExtractProjects
-
-func ExtractProjects(taskCtx plugin.SubTaskContext) errors.Error {
-       // As we need to assign data.LastAnalysisDate, we can not use 
CreateRawDataSubTaskArgs
-       data := taskCtx.GetData().(*SonarqubeTaskData)
-       var params = SonarqubeApiParams{
-               ConnectionId: data.Options.ConnectionId,
-               ProjectKey:   data.Options.ProjectKey,
-       }
-       rawDataSubTaskArgs := &helper.RawDataSubTaskArgs{
-               Ctx:    taskCtx,
-               Params: params,
-               Table:  RAW_PROJECTS_TABLE,
-       }
-       extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
-               RawDataSubTaskArgs: *rawDataSubTaskArgs,
-               Extract: func(resData *helper.RawData) ([]interface{}, 
errors.Error) {
-                       res := SonarqubeApiProject{}
-                       err := errors.Convert(json.Unmarshal(resData.Data, 
&res))
-                       if err != nil {
-                               return nil, err
-                       }
-                       body := ConvertProject(&res)
-                       body.ConnectionId = data.Options.ConnectionId
-                       data.LastAnalysisDate = 
body.LastAnalysisDate.ToNullableTime()
-                       return []interface{}{body}, nil
-               },
-       })
-       if err != nil {
-               return err
-       }
-
-       return extractor.Execute()
-}
-
-var ExtractProjectsMeta = plugin.SubTaskMeta{
-       Name:             "ExtractProjects",
-       EntryPoint:       ExtractProjects,
-       EnabledByDefault: true,
-       Description:      "Extract raw data into tool layer table 
sonarqube_projects",
-       DomainTypes:      []string{plugin.DOMAIN_TYPE_CODE_QUALITY},
-}
-
-type SonarqubeApiProject struct {
-       ProjectKey       string              `json:"key"`
-       Name             string              `json:"name"`
-       Qualifier        string              `json:"qualifier"`
-       Visibility       string              `json:"visibility"`
-       LastAnalysisDate *helper.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
-}
diff --git a/backend/plugins/sonarqube/tasks/shared.go 
b/backend/plugins/sonarqube/tasks/shared.go
index f0c4746cf..024cffcb7 100644
--- a/backend/plugins/sonarqube/tasks/shared.go
+++ b/backend/plugins/sonarqube/tasks/shared.go
@@ -21,6 +21,7 @@ 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"
        "net/http"
 )
 
@@ -62,3 +63,25 @@ type Paging struct {
        PageSize  int `json:"pageSize"`
        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
+}

Reply via email to