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

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new ddd549935 cherry-pick: implement ApiClient reuse in RemoteApiHelper 
(#6068)
ddd549935 is described below

commit ddd549935413acdb51879b8fb9d2e2c69c3b71f2
Author: Leric Zhang <[email protected]>
AuthorDate: Wed Sep 13 12:26:08 2023 +0800

    cherry-pick: implement ApiClient reuse in RemoteApiHelper (#6068)
    
    * feat: try implement common GetHash in BaseConnection (#5993) (#6025)
    
    * fix(gitlab): _raw_gitlab_api_pipeline_details table data not collected 
(#6027) (#6032)
---
 backend/core/plugin/plugin_connection_abstract.go  |  8 ++-
 backend/helpers/pluginhelper/api/connection.go     |  5 ++
 .../helpers/pluginhelper/api/remote_api_helper.go  | 70 +++++++++++++++++++---
 backend/plugins/gitlab/api/proxy.go                | 27 +--------
 backend/plugins/gitlab/tasks/pipeline_extractor.go |  2 +-
 backend/plugins/sonarqube/models/connection.go     | 16 ++---
 6 files changed, 83 insertions(+), 45 deletions(-)

diff --git a/backend/core/plugin/plugin_connection_abstract.go 
b/backend/core/plugin/plugin_connection_abstract.go
index 6a9b648c8..098dfb1fc 100644
--- a/backend/core/plugin/plugin_connection_abstract.go
+++ b/backend/core/plugin/plugin_connection_abstract.go
@@ -18,10 +18,11 @@ limitations under the License.
 package plugin
 
 import (
+       "net/http"
+
        "github.com/apache/incubator-devlake/core/errors"
        
"github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
        "github.com/go-playground/validator/v10"
-       "net/http"
 )
 
 // ApiConnection represents a API Connection
@@ -31,6 +32,11 @@ type ApiConnection interface {
        GetRateLimitPerHour() int
 }
 
+type CacheableConnection interface {
+       ApiConnection
+       GetHash() string
+}
+
 // ApiAuthenticator is to be implemented by a Concreate Connection if 
Authorization is required
 type ApiAuthenticator interface {
        // SetupAuthentication is a hook function for connection to set up 
authentication for the HTTP request
diff --git a/backend/helpers/pluginhelper/api/connection.go 
b/backend/helpers/pluginhelper/api/connection.go
index b39d3a1d6..a4877d14e 100644
--- a/backend/helpers/pluginhelper/api/connection.go
+++ b/backend/helpers/pluginhelper/api/connection.go
@@ -18,6 +18,7 @@ limitations under the License.
 package api
 
 import (
+       "fmt"
        "strings"
 
        "github.com/apache/incubator-devlake/core/models/common"
@@ -29,6 +30,10 @@ type BaseConnection struct {
        common.Model
 }
 
+func (c BaseConnection) GetHash() string {
+       return fmt.Sprintf("%d%v", c.ID, c.UpdatedAt)
+}
+
 // RestConnection implements the ApiConnection interface
 type RestConnection struct {
        Endpoint         string `mapstructure:"endpoint" validate:"required" 
json:"endpoint"`
diff --git a/backend/helpers/pluginhelper/api/remote_api_helper.go 
b/backend/helpers/pluginhelper/api/remote_api_helper.go
index 76a0f540c..80d3eeedb 100644
--- a/backend/helpers/pluginhelper/api/remote_api_helper.go
+++ b/backend/helpers/pluginhelper/api/remote_api_helper.go
@@ -18,13 +18,17 @@ limitations under the License.
 package api
 
 import (
+       gocontext "context"
        "encoding/base64"
        "encoding/json"
        "fmt"
-       "github.com/apache/incubator-devlake/core/log"
+       "io"
        "net/http"
+       "net/url"
        "strconv"
 
+       "github.com/apache/incubator-devlake/core/log"
+
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
@@ -65,10 +69,11 @@ type SearchRemoteScopesOutput struct {
 
 // RemoteApiHelper is used to write the CURD of connection
 type RemoteApiHelper[Conn plugin.ApiConnection, Scope plugin.ToolLayerScope, 
ApiScope plugin.ApiScope, Group plugin.ApiGroup] struct {
-       basicRes   context.BasicRes
-       validator  *validator.Validate
-       connHelper *ConnectionApiHelper
-       logger     log.Logger
+       basicRes        context.BasicRes
+       validator       *validator.Validate
+       connHelper      *ConnectionApiHelper
+       httpClientCache map[string]*ApiClient
+       logger          log.Logger
 }
 
 // NewRemoteHelper creates a ScopeHelper for connection management
@@ -84,10 +89,11 @@ func NewRemoteHelper[Conn plugin.ApiConnection, Scope 
plugin.ToolLayerScope, Api
                return nil
        }
        return &RemoteApiHelper[Conn, Scope, ApiScope, Group]{
-               basicRes:   basicRes,
-               validator:  vld,
-               connHelper: connHelper,
-               logger:     basicRes.GetLogger(),
+               basicRes:        basicRes,
+               validator:       vld,
+               connHelper:      connHelper,
+               httpClientCache: make(map[string]*ApiClient),
+               logger:          basicRes.GetLogger(),
        }
 }
 
@@ -122,6 +128,52 @@ const (
        TypeMixed string = "mixed"
 )
 
+func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) 
GetApiClient(connection plugin.CacheableConnection) (*ApiClient, errors.Error) {
+       key := connection.GetHash()
+       // empty key means no connection reuse
+       if key == "" {
+               r.logger.Info("No api client reuse")
+               return NewApiClientFromConnection(gocontext.TODO(), r.basicRes, 
connection)
+       }
+
+       if client, ok := r.httpClientCache[key]; ok {
+               r.logger.Info("Reused api client")
+               return client, nil
+       }
+       r.logger.Info("Creating new api client")
+       newClient, err := NewApiClientFromConnection(gocontext.TODO(), 
r.basicRes, connection)
+       if err != nil {
+               return nil, err
+       }
+       r.httpClientCache[key] = newClient
+       return newClient, nil
+}
+
+func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) ProxyApiGet(conn 
plugin.CacheableConnection, path string, query url.Values) 
(*plugin.ApiResourceOutput, errors.Error) {
+       apiClient, err := r.GetApiClient(conn)
+       if err != nil {
+               return nil, err
+       }
+
+       resp, err := apiClient.Get(path, query, nil)
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       body, err := errors.Convert01(io.ReadAll(resp.Body))
+       if err != nil {
+               return nil, err
+       }
+       // verify response body is json
+       var tmp interface{}
+       err = errors.Convert(json.Unmarshal(body, &tmp))
+       if err != nil {
+               return nil, err
+       }
+       return &plugin.ApiResourceOutput{Status: resp.StatusCode, Body: 
json.RawMessage(body)}, nil
+}
+
 // PrepareFirstPageToken prepares the first page token
 func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) 
PrepareFirstPageToken(customInfo string) (*plugin.ApiResourceOutput, 
errors.Error) {
        outputBody := &FirstPageTokenOutput{}
diff --git a/backend/plugins/gitlab/api/proxy.go 
b/backend/plugins/gitlab/api/proxy.go
index f14209c81..f9ee83910 100644
--- a/backend/plugins/gitlab/api/proxy.go
+++ b/backend/plugins/gitlab/api/proxy.go
@@ -18,14 +18,10 @@ limitations under the License.
 package api
 
 import (
-       "context"
-       "encoding/json"
-       "io"
        "time"
 
        "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/gitlab/models"
 )
 
@@ -39,26 +35,5 @@ func Proxy(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Er
        if err != nil {
                return nil, err
        }
-       apiClient, err := helper.NewApiClientFromConnection(context.TODO(), 
basicRes, connection)
-       if err != nil {
-               return nil, err
-       }
-
-       resp, err := apiClient.Get(input.Params["path"], input.Query, nil)
-       if err != nil {
-               return nil, err
-       }
-       defer resp.Body.Close()
-
-       body, err := errors.Convert01(io.ReadAll(resp.Body))
-       if err != nil {
-               return nil, err
-       }
-       // verify response body is json
-       var tmp interface{}
-       err = errors.Convert(json.Unmarshal(body, &tmp))
-       if err != nil {
-               return nil, err
-       }
-       return &plugin.ApiResourceOutput{Status: resp.StatusCode, Body: 
json.RawMessage(body)}, nil
+       return remoteHelper.ProxyApiGet(connection, input.Params["path"], 
input.Query)
 }
diff --git a/backend/plugins/gitlab/tasks/pipeline_extractor.go 
b/backend/plugins/gitlab/tasks/pipeline_extractor.go
index c18eea0e3..c07ee745d 100644
--- a/backend/plugins/gitlab/tasks/pipeline_extractor.go
+++ b/backend/plugins/gitlab/tasks/pipeline_extractor.go
@@ -106,7 +106,7 @@ func ExtractApiPipelines(taskCtx plugin.SubTaskContext) 
errors.Error {
                                IsDetailRequired: false,
                        }
 
-                       if gitlabApiPipeline.CreatedAt == nil && 
gitlabApiPipeline.UpdatedAt == nil {
+                       if gitlabApiPipeline.StartedAt == nil && 
gitlabApiPipeline.FinishedAt == nil {
                                gitlabPipeline.IsDetailRequired = true
                        }
 
diff --git a/backend/plugins/sonarqube/models/connection.go 
b/backend/plugins/sonarqube/models/connection.go
index 3309d5d28..3b8cd3125 100644
--- a/backend/plugins/sonarqube/models/connection.go
+++ b/backend/plugins/sonarqube/models/connection.go
@@ -20,10 +20,11 @@ package models
 import (
        "encoding/base64"
        "fmt"
+       "net/http"
+
        "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"
 )
 
 type SonarqubeAccessToken helper.AccessToken
@@ -43,19 +44,18 @@ func (sat SonarqubeAccessToken) GetEncodedToken() string {
        return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%v:", 
sat.Token)))
 }
 
-// This object conforms to what the frontend currently sends.
-type SonarqubeConnection struct {
-       helper.BaseConnection `mapstructure:",squash"`
-       helper.RestConnection `mapstructure:",squash"`
-       SonarqubeAccessToken  `mapstructure:",squash"`
-}
-
 // SonarqubeConn holds the essential information to connect to the sonarqube 
API
 type SonarqubeConn struct {
        helper.RestConnection `mapstructure:",squash"`
        SonarqubeAccessToken  `mapstructure:",squash"`
 }
 
+// This object conforms to what the frontend currently sends.
+type SonarqubeConnection struct {
+       helper.BaseConnection `mapstructure:",squash"`
+       SonarqubeConn         `mapstructure:",squash"`
+}
+
 // This object conforms to what the frontend currently expects.
 type SonarqubeResponse struct {
        Name string `json:"name"`

Reply via email to