You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by zh...@apache.org on 2023/02/07 12:12:29 UTC

[incubator-devlake] branch main updated: refactor: unify all apiclient creation for all plugins (#4346)

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

zhangliang2022 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 145431ae2 refactor: unify all apiclient creation for all plugins (#4346)
145431ae2 is described below

commit 145431ae2dd69cd40f5cad3a83fcc72059624cb2
Author: Klesh Wong <zh...@merico.dev>
AuthorDate: Tue Feb 7 20:12:23 2023 +0800

    refactor: unify all apiclient creation for all plugins (#4346)
    
    * refactor: unify all apiclient creation for all plugins
    
    * fix: linting
---
 backend/helpers/pluginhelper/api/api_client.go     | 17 +----
 backend/plugins/bitbucket/api/blueprint.go         | 40 +++++-----
 backend/plugins/bitbucket/api/blueprint_test.go    |  4 +-
 backend/plugins/bitbucket/tasks/task_data.go       |  3 +-
 backend/plugins/gitee/api/blueprint.go             | 36 +++++----
 backend/plugins/gitee/api/blueprint_test.go        |  4 +-
 backend/plugins/github/api/blueprint.go            | 40 +++++-----
 backend/plugins/github/api/blueprint_test.go       |  7 +-
 backend/plugins/github/api/connection.go           | 88 +++++-----------------
 backend/plugins/github/api/proxy.go                | 19 +----
 backend/plugins/gitlab/api/blueprint.go            | 24 ++++--
 backend/plugins/gitlab/api/blueprint_test.go       |  4 +-
 backend/plugins/gitlab/api/blueprint_v200.go       | 22 +++++-
 backend/plugins/gitlab/api/proxy.go                | 17 +----
 backend/plugins/jenkins/api/blueprint_v100.go      | 21 ++----
 backend/plugins/jenkins/api/blueprint_v100_test.go |  4 +-
 backend/plugins/jenkins/api/jobs.go                | 25 +++++-
 backend/plugins/jenkins/api/jobs_test.go           |  9 ++-
 backend/plugins/jira/api/proxy.go                  | 16 +---
 backend/plugins/jira/api/scope.go                  | 10 ++-
 20 files changed, 181 insertions(+), 229 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/api_client.go b/backend/helpers/pluginhelper/api/api_client.go
index c25230f04..c2688bf16 100644
--- a/backend/helpers/pluginhelper/api/api_client.go
+++ b/backend/helpers/pluginhelper/api/api_client.go
@@ -35,22 +35,13 @@ import (
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/log"
 	"github.com/apache/incubator-devlake/core/utils"
-	"github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/common"
 )
 
 // ErrIgnoreAndContinue is a error which should be ignored
 var ErrIgnoreAndContinue = errors.Default.New("ignore and continue")
 
-// ApiClientGetter will be used for uint test
-type ApiClientGetter interface {
-	Get(
-		path string,
-		query url.Values,
-		headers http.Header,
-	) (*http.Response, errors.Error)
-}
-
 // ApiClient is designed for simple api requests
 type ApiClient struct {
 	client        *http.Client
@@ -66,21 +57,21 @@ type ApiClient struct {
 func NewApiClientFromConnection(
 	ctx gocontext.Context,
 	br context.BasicRes,
-	connection apihelperabstract.ApiConnection,
+	connection aha.ApiConnection,
 ) (*ApiClient, errors.Error) {
 	apiClient, err := NewApiClient(ctx, connection.GetEndpoint(), nil, 0, connection.GetProxy(), br)
 	if err != nil {
 		return nil, err
 	}
 	// if connection needs to prepare the ApiClient, i.e. fetch token for future requests
-	if prepareApiClient, ok := connection.(apihelperabstract.PrepareApiClient); ok {
+	if prepareApiClient, ok := connection.(aha.PrepareApiClient); ok {
 		err = prepareApiClient.PrepareApiClient(apiClient)
 		if err != nil {
 			return nil, err
 		}
 	}
 	// if connection requires authorization
-	if authenticator, ok := connection.(apihelperabstract.ApiAuthenticator); ok {
+	if authenticator, ok := connection.(aha.ApiAuthenticator); ok {
 		apiClient.SetBeforeFunction(func(req *http.Request) errors.Error {
 			return authenticator.SetupAuthentication(req)
 		})
diff --git a/backend/plugins/bitbucket/api/blueprint.go b/backend/plugins/bitbucket/api/blueprint.go
index 06e38ae7a..076c5a6e2 100644
--- a/backend/plugins/bitbucket/api/blueprint.go
+++ b/backend/plugins/bitbucket/api/blueprint.go
@@ -21,19 +21,19 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"path"
+
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/apache/incubator-devlake/core/utils"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/bitbucket/models"
 	"github.com/apache/incubator-devlake/plugins/bitbucket/tasks"
-	"io"
-	"net/http"
-	"net/url"
-	"path"
-	"strings"
-	"time"
 )
 
 func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) {
@@ -42,28 +42,19 @@ func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, sc
 	if err != nil {
 		return nil, err
 	}
-	tokens := strings.Split(connection.GetEncodedToken(), ",")
-	if len(tokens) == 0 {
-		return nil, errors.Default.New("no token")
-	}
-	token := tokens[0]
-	apiClient, err := api.NewApiClient(
-		context.TODO(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Basic %s", token),
-		},
-		10*time.Second,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
 	return makePipelinePlan(subtaskMetas, scope, apiClient, connection)
 }
 
-func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.BlueprintScopeV100, apiClient api.ApiClientGetter, connection *models.BitbucketConnection) (plugin.PipelinePlan, errors.Error) {
+func makePipelinePlan(
+	subtaskMetas []plugin.SubTaskMeta,
+	scope []*plugin.BlueprintScopeV100,
+	apiClient aha.ApiClientAbstract,
+	connection *models.BitbucketConnection,
+) (plugin.PipelinePlan, errors.Error) {
 	var err errors.Error
 	plan := make(plugin.PipelinePlan, len(scope))
 	var repo *tasks.BitbucketApiRepo
@@ -157,7 +148,10 @@ func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.Bluepri
 	return plan, nil
 }
 
-func getApiRepo(op *tasks.BitbucketOptions, apiClient api.ApiClientGetter) (*tasks.BitbucketApiRepo, errors.Error) {
+func getApiRepo(
+	op *tasks.BitbucketOptions,
+	apiClient aha.ApiClientAbstract,
+) (*tasks.BitbucketApiRepo, errors.Error) {
 	res, err := apiClient.Get(path.Join("repositories", op.Owner, op.Repo), nil, nil)
 	if err != nil {
 		return nil, err
diff --git a/backend/plugins/bitbucket/api/blueprint_test.go b/backend/plugins/bitbucket/api/blueprint_test.go
index 43b875178..6db0aaa1e 100644
--- a/backend/plugins/bitbucket/api/blueprint_test.go
+++ b/backend/plugins/bitbucket/api/blueprint_test.go
@@ -29,7 +29,7 @@ import (
 	"github.com/apache/incubator-devlake/core/plugin"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 	mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
-	mockapi "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api"
+	mockaha "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/bitbucket/models"
 	"github.com/apache/incubator-devlake/plugins/bitbucket/tasks"
 	"github.com/stretchr/testify/assert"
@@ -57,7 +57,7 @@ func TestMakePipelinePlan(t *testing.T) {
 		},
 	}
 
-	mockApiCLient := mockapi.NewApiClientGetter(t)
+	mockApiCLient := mockaha.NewApiClientAbstract(t)
 	repo := &tasks.BitbucketApiRepo{
 		Links: struct {
 			Clone []struct {
diff --git a/backend/plugins/bitbucket/tasks/task_data.go b/backend/plugins/bitbucket/tasks/task_data.go
index 6d5dc96bd..4a8150878 100644
--- a/backend/plugins/bitbucket/tasks/task_data.go
+++ b/backend/plugins/bitbucket/tasks/task_data.go
@@ -18,10 +18,11 @@ limitations under the License.
 package tasks
 
 import (
+	"time"
+
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 	"github.com/apache/incubator-devlake/plugins/bitbucket/models"
-	"time"
 )
 
 type BitbucketOptions struct {
diff --git a/backend/plugins/gitee/api/blueprint.go b/backend/plugins/gitee/api/blueprint.go
index 452d4f667..9ed199f4e 100644
--- a/backend/plugins/gitee/api/blueprint.go
+++ b/backend/plugins/gitee/api/blueprint.go
@@ -21,18 +21,19 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/apache/incubator-devlake/core/utils"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/gitee/models"
 	"github.com/apache/incubator-devlake/plugins/gitee/tasks"
-	"io"
-	"net/http"
-	"net/url"
-	"strings"
-	"time"
 )
 
 func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) {
@@ -42,18 +43,7 @@ func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, sc
 	if err != nil {
 		return nil, err
 	}
-	token := strings.Split(connection.Token, ",")[0]
-
-	apiClient, err := api.NewApiClient(
-		context.TODO(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Bearer %s", token),
-		},
-		10*time.Second,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
@@ -64,7 +54,12 @@ func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, sc
 	return plan, nil
 }
 
-func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.BlueprintScopeV100, apiClient api.ApiClientGetter, connection *models.GiteeConnection) (plugin.PipelinePlan, errors.Error) {
+func makePipelinePlan(
+	subtaskMetas []plugin.SubTaskMeta,
+	scope []*plugin.BlueprintScopeV100,
+	apiClient aha.ApiClientAbstract,
+	connection *models.GiteeConnection,
+) (plugin.PipelinePlan, errors.Error) {
 	var err errors.Error
 	var repo *tasks.GiteeApiRepoResponse
 	plan := make(plugin.PipelinePlan, len(scope))
@@ -188,7 +183,10 @@ func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.Bluepri
 	return plan, nil
 }
 
-func getApiRepo(op *tasks.GiteeOptions, apiClient api.ApiClientGetter) (*tasks.GiteeApiRepoResponse, errors.Error) {
+func getApiRepo(
+	op *tasks.GiteeOptions,
+	apiClient aha.ApiClientAbstract,
+) (*tasks.GiteeApiRepoResponse, errors.Error) {
 	apiRepo := &tasks.GiteeApiRepoResponse{}
 	res, err := apiClient.Get(fmt.Sprintf("repos/%s/%s", op.Owner, op.Repo), nil, nil)
 	if err != nil {
diff --git a/backend/plugins/gitee/api/blueprint_test.go b/backend/plugins/gitee/api/blueprint_test.go
index 226464ab9..b756e2a2f 100644
--- a/backend/plugins/gitee/api/blueprint_test.go
+++ b/backend/plugins/gitee/api/blueprint_test.go
@@ -28,7 +28,7 @@ import (
 	"github.com/apache/incubator-devlake/core/plugin"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 	mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
-	mockapi "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api"
+	mockaha "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/gitee/models"
 	"github.com/apache/incubator-devlake/plugins/gitee/tasks"
 	"github.com/stretchr/testify/assert"
@@ -55,7 +55,7 @@ func TestMakePipelinePlan(t *testing.T) {
 			},
 		},
 	}
-	mockApiCLient := mockapi.NewApiClientGetter(t)
+	mockApiCLient := mockaha.NewApiClientAbstract(t)
 	repo := &tasks.GiteeApiRepoResponse{
 		GiteeId: 12345,
 		HTMLUrl: "https://this_is_cloneUrl",
diff --git a/backend/plugins/github/api/blueprint.go b/backend/plugins/github/api/blueprint.go
index 4f2e848ca..4f3ab62d0 100644
--- a/backend/plugins/github/api/blueprint.go
+++ b/backend/plugins/github/api/blueprint.go
@@ -21,39 +21,34 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/apache/incubator-devlake/core/errors"
 	"io"
 	"net/http"
 	"net/url"
 	"strings"
-	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
 
 	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
 	"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/github/models"
 	"github.com/apache/incubator-devlake/plugins/github/tasks"
 )
 
-func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) {
+func MakePipelinePlan(
+	subtaskMetas []plugin.SubTaskMeta,
+	connectionId uint64,
+	scope []*plugin.BlueprintScopeV100,
+) (plugin.PipelinePlan, errors.Error) {
 	var err errors.Error
 	connection := new(models.GithubConnection)
 	err = connectionHelper.FirstById(connection, connectionId)
 	if err != nil {
 		return nil, err
 	}
-	token := strings.Split(connection.Token, ",")[0]
-	apiClient, err := helper.NewApiClient(
-		context.TODO(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Bearer %s", token),
-		},
-		10*time.Second,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := helper.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
@@ -64,7 +59,12 @@ func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, sc
 	return plan, nil
 }
 
-func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scopeV100s []*plugin.BlueprintScopeV100, apiClient helper.ApiClientGetter, connection *models.GithubConnection) (plugin.PipelinePlan, errors.Error) {
+func makePipelinePlan(
+	subtaskMetas []plugin.SubTaskMeta,
+	scopeV100s []*plugin.BlueprintScopeV100,
+	apiClient aha.ApiClientAbstract,
+	connection *models.GithubConnection,
+) (plugin.PipelinePlan, errors.Error) {
 	var err errors.Error
 	var repo *tasks.GithubApiRepo
 	plan := make(plugin.PipelinePlan, len(scopeV100s))
@@ -220,7 +220,10 @@ func addGithub(subtaskMetas []plugin.SubTaskMeta, connection *models.GithubConne
 	return stage, nil
 }
 
-func getApiRepo(op *tasks.GithubOptions, apiClient helper.ApiClientGetter) (*tasks.GithubApiRepo, errors.Error) {
+func getApiRepo(
+	op *tasks.GithubOptions,
+	apiClient aha.ApiClientAbstract,
+) (*tasks.GithubApiRepo, errors.Error) {
 	repoRes := &tasks.GithubApiRepo{}
 	res, err := apiClient.Get(fmt.Sprintf("repos/%s", op.Name), nil, nil)
 	if err != nil {
@@ -241,7 +244,10 @@ func getApiRepo(op *tasks.GithubOptions, apiClient helper.ApiClientGetter) (*tas
 	return repoRes, nil
 }
 
-func MemorizedGetApiRepo(repo *tasks.GithubApiRepo, op *tasks.GithubOptions, apiClient helper.ApiClientGetter) (*tasks.GithubApiRepo, errors.Error) {
+func MemorizedGetApiRepo(
+	repo *tasks.GithubApiRepo,
+	op *tasks.GithubOptions, apiClient aha.ApiClientAbstract,
+) (*tasks.GithubApiRepo, errors.Error) {
 	if repo == nil {
 		var err errors.Error
 		repo, err = getApiRepo(op, apiClient)
diff --git a/backend/plugins/github/api/blueprint_test.go b/backend/plugins/github/api/blueprint_test.go
index f6193e05d..58170b75b 100644
--- a/backend/plugins/github/api/blueprint_test.go
+++ b/backend/plugins/github/api/blueprint_test.go
@@ -28,8 +28,9 @@ import (
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/plugin"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 	mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
-	mockapi "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api"
+	mockaha "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/github/models"
 	"github.com/apache/incubator-devlake/plugins/github/tasks"
 	"github.com/stretchr/testify/assert"
@@ -159,8 +160,8 @@ func prepareMockMeta(t *testing.T) {
 	assert.Nil(t, err)
 }
 
-func prepareMockClient(t *testing.T, repo *tasks.GithubApiRepo) *mockapi.ApiClientGetter {
-	mockApiCLient := mockapi.NewApiClientGetter(t)
+func prepareMockClient(t *testing.T, repo *tasks.GithubApiRepo) aha.ApiClientAbstract {
+	mockApiCLient := mockaha.NewApiClientAbstract(t)
 	js, err := json.Marshal(repo)
 	assert.Nil(t, err)
 	res := &http.Response{}
diff --git a/backend/plugins/github/api/connection.go b/backend/plugins/github/api/connection.go
index fbb90ab4b..958328311 100644
--- a/backend/plugins/github/api/connection.go
+++ b/backend/plugins/github/api/connection.go
@@ -19,10 +19,7 @@ package api
 
 import (
 	"context"
-	"fmt"
 	"net/http"
-	"strings"
-	"time"
 
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/plugin"
@@ -46,83 +43,36 @@ type GithubTestConnResponse struct {
 // @Router /plugins/github/test [POST]
 func TestConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
 	// process input
-	var params models.GithubConn
-	err := api.Decode(input.Body, &params, vld)
+	var conn models.GithubConn
+	err := api.Decode(input.Body, &conn, vld)
 	if err != nil {
 		return nil, err
 	}
 
-	tokens := strings.Split(params.Token, ",")
-
-	// verify multiple token in parallel
-	type VerifyResult struct {
-		err   errors.Error
-		login string
+	apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, conn)
+	if err != nil {
+		return nil, err
 	}
-	results := make(chan VerifyResult)
-	for i := 0; i < len(tokens); i++ {
-		token := tokens[i]
-		j := i + 1
-		go func() {
-			apiClient, err := api.NewApiClient(
-				context.TODO(),
-				params.Endpoint,
-				map[string]string{
-					"Authorization": fmt.Sprintf("Bearer %s", token),
-				},
-				3*time.Second,
-				params.Proxy,
-				basicRes,
-			)
-			if err != nil {
-				results <- VerifyResult{err: errors.BadInput.Wrap(err, fmt.Sprintf("verify token failed for #%d %s", j, token))}
-				return
-			}
-			res, err := apiClient.Get("user", nil, nil)
-			if err != nil {
-				results <- VerifyResult{err: errors.Default.Wrap(err, fmt.Sprintf("verify token failed for #%d %s", j, token))}
-				return
-			}
-			if res.StatusCode != http.StatusOK {
-				results <- VerifyResult{err: errors.HttpStatus(res.StatusCode).New("unexpected status code while testing connection")}
-				return
-			}
-
-			githubUserOfToken := &models.GithubUserOfToken{}
-			err = api.UnmarshalResponse(res, githubUserOfToken)
-			if err != nil {
-				results <- VerifyResult{err: errors.BadInput.Wrap(err, fmt.Sprintf("verify token failed for #%v %s", j, token))}
-				return
-			} else if githubUserOfToken.Login == "" {
-				results <- VerifyResult{err: errors.BadInput.Wrap(err, fmt.Sprintf("invalid token for #%v %s", j, token))}
-				return
-			}
-
-			results <- VerifyResult{login: githubUserOfToken.Login}
-		}()
+	res, err := apiClient.Get("user", nil, nil)
+	if err != nil {
+		return nil, errors.BadInput.Wrap(err, "verify token failed")
 	}
-	// collect verification results
-	logins := make([]string, 0)
-	allErrors := make([]error, 0)
-	i := 0
-	for result := range results {
-		if result.err != nil {
-			allErrors = append(allErrors, result.err)
-		}
-		logins = append(logins, result.login)
-		i++
-		if i == len(tokens) {
-			close(results)
-		}
+	if res.StatusCode != http.StatusOK {
+		return nil, errors.HttpStatus(res.StatusCode).New("unexpected status code while testing connection")
 	}
-	if len(allErrors) > 0 {
-		return nil, errors.Default.Combine(allErrors)
+
+	githubUserOfToken := &models.GithubUserOfToken{}
+	err = api.UnmarshalResponse(res, githubUserOfToken)
+	if err != nil {
+		return nil, errors.BadInput.Wrap(err, "verify token failed")
+	} else if githubUserOfToken.Login == "" {
+		return nil, errors.BadInput.Wrap(err, "invalid token")
 	}
 
-	githubApiResponse := GithubTestConnResponse{}
+	githubApiResponse := &GithubTestConnResponse{}
 	githubApiResponse.Success = true
 	githubApiResponse.Message = "success"
-	githubApiResponse.Login = strings.Join(logins, `,`)
+	githubApiResponse.Login = githubUserOfToken.Login
 	return &plugin.ApiResourceOutput{Body: githubApiResponse, Status: http.StatusOK}, nil
 }
 
diff --git a/backend/plugins/github/api/proxy.go b/backend/plugins/github/api/proxy.go
index 68f3de2c2..03ed5c231 100644
--- a/backend/plugins/github/api/proxy.go
+++ b/backend/plugins/github/api/proxy.go
@@ -20,14 +20,13 @@ package api
 import (
 	"context"
 	"encoding/json"
-	"fmt"
+	"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/github/models"
-	"io"
-	"strings"
-	"time"
 )
 
 const (
@@ -40,17 +39,7 @@ func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Er
 	if err != nil {
 		return nil, err
 	}
-	tokens := strings.Split(connection.Token, ",")
-	apiClient, err := helper.NewApiClient(
-		context.TODO(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Bearer %s", tokens[0]),
-		},
-		TimeOut,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := helper.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
diff --git a/backend/plugins/gitlab/api/blueprint.go b/backend/plugins/gitlab/api/blueprint.go
index fc9d2314e..df9a96be3 100644
--- a/backend/plugins/gitlab/api/blueprint.go
+++ b/backend/plugins/gitlab/api/blueprint.go
@@ -21,18 +21,20 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/apache/incubator-devlake/core/utils"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/gitlab/models"
 	"github.com/apache/incubator-devlake/plugins/gitlab/tasks"
-	"io"
-	"net/http"
-	"net/url"
-	"strings"
-	"time"
 )
 
 func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) {
@@ -64,7 +66,12 @@ func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, sc
 	return plan, nil
 }
 
-func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.BlueprintScopeV100, apiClient api.ApiClientGetter, connection *models.GitlabConnection) (plugin.PipelinePlan, errors.Error) {
+func makePipelinePlan(
+	subtaskMetas []plugin.SubTaskMeta,
+	scope []*plugin.BlueprintScopeV100,
+	apiClient aha.ApiClientAbstract,
+	connection *models.GitlabConnection,
+) (plugin.PipelinePlan, errors.Error) {
 	var err errors.Error
 	var repo *tasks.GitlabApiProject
 	plan := make(plugin.PipelinePlan, len(scope))
@@ -192,7 +199,10 @@ func makePipelinePlan(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.Bluepri
 	return plan, nil
 }
 
-func getApiRepo(op *tasks.GitlabOptions, apiClient api.ApiClientGetter) (*tasks.GitlabApiProject, errors.Error) {
+func getApiRepo(
+	op *tasks.GitlabOptions,
+	apiClient aha.ApiClientAbstract,
+) (*tasks.GitlabApiProject, errors.Error) {
 	apiRepo := &tasks.GitlabApiProject{}
 	res, err := apiClient.Get(fmt.Sprintf("projects/%d", op.ProjectId), nil, nil)
 	if err != nil {
diff --git a/backend/plugins/gitlab/api/blueprint_test.go b/backend/plugins/gitlab/api/blueprint_test.go
index ef619e306..1d7ed10c9 100644
--- a/backend/plugins/gitlab/api/blueprint_test.go
+++ b/backend/plugins/gitlab/api/blueprint_test.go
@@ -28,7 +28,7 @@ import (
 	"github.com/apache/incubator-devlake/core/plugin"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 	mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
-	mockapi "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api"
+	mockaha "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/gitlab/models"
 	"github.com/apache/incubator-devlake/plugins/gitlab/tasks"
 	"github.com/stretchr/testify/assert"
@@ -54,7 +54,7 @@ func TestProcessScope(t *testing.T) {
 			},
 		},
 	}
-	mockApiCLient := mockapi.NewApiClientGetter(t)
+	mockApiCLient := mockaha.NewApiClientAbstract(t)
 	repo := &tasks.GitlabApiProject{
 		GitlabId:      12345,
 		HttpUrlToRepo: "https://this_is_HttpUrlToRepo",
diff --git a/backend/plugins/gitlab/api/blueprint_v200.go b/backend/plugins/gitlab/api/blueprint_v200.go
index a3cb61173..a19ee9c50 100644
--- a/backend/plugins/gitlab/api/blueprint_v200.go
+++ b/backend/plugins/gitlab/api/blueprint_v200.go
@@ -20,13 +20,14 @@ package api
 import (
 	"encoding/json"
 	"fmt"
-	"github.com/apache/incubator-devlake/plugins/gitlab/tasks"
 	"io"
 	"net/http"
 	"net/url"
 	"strconv"
 	"time"
 
+	"github.com/apache/incubator-devlake/plugins/gitlab/tasks"
+
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/utils"
 
@@ -37,10 +38,16 @@ import (
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	plugin "github.com/apache/incubator-devlake/core/plugin"
 	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/gitlab/models"
 )
 
-func MakePipelinePlanV200(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, scope []*plugin.BlueprintScopeV200, syncPolicy *plugin.BlueprintSyncPolicy) (plugin.PipelinePlan, []plugin.Scope, errors.Error) {
+func MakePipelinePlanV200(
+	subtaskMetas []plugin.SubTaskMeta,
+	connectionId uint64,
+	scope []*plugin.BlueprintScopeV200,
+	syncPolicy *plugin.BlueprintSyncPolicy,
+) (plugin.PipelinePlan, []plugin.Scope, errors.Error) {
 	var err errors.Error
 	connection := new(models.GitlabConnection)
 	err1 := connectionHelper.FirstById(connection, connectionId)
@@ -106,7 +113,11 @@ func makeScopeV200(connectionId uint64, scopes []*plugin.BlueprintScopeV200) ([]
 	return sc, nil
 }
 
-func makePipelinePlanV200(subtaskMetas []plugin.SubTaskMeta, scopes []*plugin.BlueprintScopeV200, connection *models.GitlabConnection, syncPolicy *plugin.BlueprintSyncPolicy) (plugin.PipelinePlan, errors.Error) {
+func makePipelinePlanV200(
+	subtaskMetas []plugin.SubTaskMeta,
+	scopes []*plugin.BlueprintScopeV200,
+	connection *models.GitlabConnection, syncPolicy *plugin.BlueprintSyncPolicy,
+) (plugin.PipelinePlan, errors.Error) {
 	plans := make(plugin.PipelinePlan, 0, 3*len(scopes))
 	for _, scope := range scopes {
 		var stage plugin.PipelineStage
@@ -220,7 +231,10 @@ func GetTransformationRuleByRepo(repo *models.GitlabProject) (*models.GitlabTran
 	return transformationRules, nil
 }
 
-func GetApiProject(op *tasks.GitlabOptions, apiClient helper.ApiClientGetter) (*tasks.GitlabApiProject, errors.Error) {
+func GetApiProject(
+	op *tasks.GitlabOptions,
+	apiClient aha.ApiClientAbstract,
+) (*tasks.GitlabApiProject, errors.Error) {
 	repoRes := &tasks.GitlabApiProject{}
 	res, err := apiClient.Get(fmt.Sprintf("projects/%d", op.ProjectId), nil, nil)
 	if err != nil {
diff --git a/backend/plugins/gitlab/api/proxy.go b/backend/plugins/gitlab/api/proxy.go
index 0b58e4200..f14209c81 100644
--- a/backend/plugins/gitlab/api/proxy.go
+++ b/backend/plugins/gitlab/api/proxy.go
@@ -20,13 +20,13 @@ package api
 import (
 	"context"
 	"encoding/json"
-	"fmt"
+	"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"
-	"io"
-	"time"
 )
 
 const (
@@ -39,16 +39,7 @@ func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Er
 	if err != nil {
 		return nil, err
 	}
-	apiClient, err := helper.NewApiClient(
-		context.TODO(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Bearer %v", connection.Token),
-		},
-		TimeOut,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := helper.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
diff --git a/backend/plugins/jenkins/api/blueprint_v100.go b/backend/plugins/jenkins/api/blueprint_v100.go
index cd7fdf932..fa7985ce9 100644
--- a/backend/plugins/jenkins/api/blueprint_v100.go
+++ b/backend/plugins/jenkins/api/blueprint_v100.go
@@ -20,14 +20,13 @@ package api
 import (
 	"context"
 	"encoding/json"
-	"fmt"
-	"time"
 
 	"github.com/apache/incubator-devlake/plugins/jenkins/models"
 
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/plugin"
 	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/jenkins/tasks"
 )
 
@@ -39,16 +38,7 @@ func MakePipelinePlanV100(subtaskMetas []plugin.SubTaskMeta, connectionId uint64
 		return nil, err
 	}
 
-	apiClient, err := helper.NewApiClient(
-		context.Background(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Basic %s", connection.GetEncodedToken()),
-		},
-		10*time.Second,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := helper.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
@@ -60,7 +50,12 @@ func MakePipelinePlanV100(subtaskMetas []plugin.SubTaskMeta, connectionId uint64
 	return plan, nil
 }
 
-func makePipelinePlanV100(subtaskMetas []plugin.SubTaskMeta, scope []*plugin.BlueprintScopeV100, connection *models.JenkinsConnection, apiClient helper.ApiClientGetter) (plugin.PipelinePlan, errors.Error) {
+func makePipelinePlanV100(
+	subtaskMetas []plugin.SubTaskMeta,
+	scope []*plugin.BlueprintScopeV100,
+	connection *models.JenkinsConnection,
+	apiClient aha.ApiClientAbstract,
+) (plugin.PipelinePlan, errors.Error) {
 	var err errors.Error
 	plans := make(plugin.PipelinePlan, 0, len(scope))
 
diff --git a/backend/plugins/jenkins/api/blueprint_v100_test.go b/backend/plugins/jenkins/api/blueprint_v100_test.go
index 630f7a0f1..5c82444f2 100644
--- a/backend/plugins/jenkins/api/blueprint_v100_test.go
+++ b/backend/plugins/jenkins/api/blueprint_v100_test.go
@@ -24,7 +24,7 @@ import (
 	"net/http"
 	"testing"
 
-	mockapi "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api"
+	mockaha "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api/apihelperabstract"
 
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/plugin"
@@ -66,7 +66,7 @@ func TestProcessScope(t *testing.T) {
 	scopes := make([]*plugin.BlueprintScopeV100, 0)
 	scopes = append(scopes, bs)
 
-	mockApiClient := mockapi.NewApiClientGetter(t)
+	mockApiClient := mockaha.NewApiClientAbstract(t)
 
 	var remoteData = []*models.Job{
 		{
diff --git a/backend/plugins/jenkins/api/jobs.go b/backend/plugins/jenkins/api/jobs.go
index 29ddbc937..992c11449 100644
--- a/backend/plugins/jenkins/api/jobs.go
+++ b/backend/plugins/jenkins/api/jobs.go
@@ -27,9 +27,15 @@ import (
 
 	"github.com/apache/incubator-devlake/core/errors"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 )
 
-func GetJobs(apiClient helper.ApiClientGetter, path string, pageSize int, callback func(job *models.Job) errors.Error) errors.Error {
+func GetJobs(
+	apiClient aha.ApiClientAbstract,
+	path string,
+	pageSize int,
+	callback func(job *models.Job) errors.Error,
+) errors.Error {
 	for i := 0; ; i += pageSize {
 		var data struct {
 			Jobs []json.RawMessage `json:"jobs"`
@@ -75,7 +81,14 @@ func GetJobs(apiClient helper.ApiClientGetter, path string, pageSize int, callba
 	}
 }
 
-func GetJob(apiClient helper.ApiClientGetter, path string, name string, fullName string, pageSize int, callback func(job *models.Job, isPath bool) errors.Error) errors.Error {
+func GetJob(
+	apiClient aha.ApiClientAbstract,
+	path string,
+	name string,
+	fullName string,
+	pageSize int,
+	callback func(job *models.Job, isPath bool) errors.Error,
+) errors.Error {
 	var err errors.Error
 
 	return GetJobs(apiClient, path, pageSize, func(job *models.Job) errors.Error {
@@ -99,7 +112,13 @@ func GetJob(apiClient helper.ApiClientGetter, path string, name string, fullName
 }
 
 // request all jobs
-func GetAllJobs(apiClient helper.ApiClientGetter, path string, beforename string, pageSize int, callback func(job *models.Job, isPath bool) errors.Error) errors.Error {
+func GetAllJobs(
+	apiClient aha.ApiClientAbstract,
+	path string,
+	beforename string,
+	pageSize int,
+	callback func(job *models.Job, isPath bool) errors.Error,
+) errors.Error {
 	var err errors.Error
 	return GetJobs(apiClient, path, pageSize, func(job *models.Job) errors.Error {
 		job.Path = path
diff --git a/backend/plugins/jenkins/api/jobs_test.go b/backend/plugins/jenkins/api/jobs_test.go
index cb8f8619c..411939b6f 100644
--- a/backend/plugins/jenkins/api/jobs_test.go
+++ b/backend/plugins/jenkins/api/jobs_test.go
@@ -20,12 +20,13 @@ package api
 import (
 	"bytes"
 	"encoding/json"
-	mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
-	mockapi "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api"
 	"io"
 	"net/http"
 	"testing"
 
+	mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
+	mockaha "github.com/apache/incubator-devlake/mocks/helpers/pluginhelper/api/apihelperabstract"
+
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/helpers/unithelper"
 	"github.com/apache/incubator-devlake/plugins/jenkins/models"
@@ -67,7 +68,7 @@ func TestGetJob(t *testing.T) {
 		},
 	}
 
-	mockApiClient := mockapi.NewApiClientGetter(t)
+	mockApiClient := mockaha.NewApiClientAbstract(t)
 
 	var data struct {
 		Jobs []json.RawMessage `json:"jobs"`
@@ -217,7 +218,7 @@ func TestGetAllJobs(t *testing.T) {
 		},
 	}
 
-	mockApiClient := mockapi.NewApiClientGetter(t)
+	mockApiClient := mockaha.NewApiClientAbstract(t)
 
 	var data struct {
 		Jobs []json.RawMessage `json:"jobs"`
diff --git a/backend/plugins/jira/api/proxy.go b/backend/plugins/jira/api/proxy.go
index 28e47d62d..8454b300b 100644
--- a/backend/plugins/jira/api/proxy.go
+++ b/backend/plugins/jira/api/proxy.go
@@ -19,13 +19,12 @@ package api
 
 import (
 	"context"
-	"fmt"
+	"io"
+
 	"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/jira/models"
-	"io"
-	"time"
 )
 
 func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
@@ -34,16 +33,7 @@ func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Er
 	if err != nil {
 		return nil, err
 	}
-	apiClient, err := helper.NewApiClient(
-		context.TODO(),
-		connection.Endpoint,
-		map[string]string{
-			"Authorization": fmt.Sprintf("Basic %v", connection.GetEncodedToken()),
-		},
-		30*time.Second,
-		connection.Proxy,
-		basicRes,
-	)
+	apiClient, err := helper.NewApiClientFromConnection(context.TODO(), basicRes, connection)
 	if err != nil {
 		return nil, err
 	}
diff --git a/backend/plugins/jira/api/scope.go b/backend/plugins/jira/api/scope.go
index 2e3ea3fcf..533d0bab3 100644
--- a/backend/plugins/jira/api/scope.go
+++ b/backend/plugins/jira/api/scope.go
@@ -20,17 +20,19 @@ package api
 import (
 	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"strconv"
+
 	"github.com/apache/incubator-devlake/core/dal"
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	aha "github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
 	"github.com/apache/incubator-devlake/plugins/jira/models"
 	"github.com/apache/incubator-devlake/plugins/jira/tasks"
 	"github.com/apache/incubator-devlake/plugins/jira/tasks/apiv2models"
 	"github.com/mitchellh/mapstructure"
-	"io"
-	"net/http"
-	"strconv"
 )
 
 type apiBoard struct {
@@ -216,7 +218,7 @@ func verifyBoard(board *models.JiraBoard) errors.Error {
 	return nil
 }
 
-func GetApiJira(op *tasks.JiraOptions, apiClient api.ApiClientGetter) (*apiv2models.Board, errors.Error) {
+func GetApiJira(op *tasks.JiraOptions, apiClient aha.ApiClientAbstract) (*apiv2models.Board, errors.Error) {
 	boardRes := &apiv2models.Board{}
 	res, err := apiClient.Get(fmt.Sprintf("agile/1.0/board/%d", op.BoardId), nil, nil)
 	if err != nil {