You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2024/01/19 02:55:28 UTC

(incubator-devlake) 03/03: refactor: bamboo adopts new dshelpers

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

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

commit f314a93faf38bf7efc61e26bcd8cdd46cb9dc47f
Author: Klesh Wong <zh...@merico.dev>
AuthorDate: Thu Jan 18 17:13:05 2024 +0800

    refactor: bamboo adopts new dshelpers
---
 .../pluginhelper/api/api_collector_with_state.go   |   2 +-
 backend/plugins/bamboo/api/blueprint_V200_test.go  | 187 ---------------------
 backend/plugins/bamboo/api/blueprint_v200.go       | 108 ++++++------
 .../api/{connection.go => connection_api.go}       |  47 +-----
 backend/plugins/bamboo/api/remote.go               | 145 ----------------
 backend/plugins/bamboo/api/remote_api.go           | 182 ++++++++++++++++++++
 .../plugins/bamboo/api/{scope.go => scope_api.go}  |  35 ++--
 .../api/{scope_config.go => scope_config_api.go}   |  14 +-
 backend/plugins/bamboo/e2e/deploy_build_test.go    |   7 +-
 backend/plugins/bamboo/e2e/deploy_test.go          |   2 +-
 backend/plugins/bamboo/e2e/job_build_test.go       |   5 +-
 backend/plugins/bamboo/e2e/job_test.go             |   2 +-
 .../plugins/bamboo/e2e/plan_build_commits_test.go  |   2 +-
 backend/plugins/bamboo/e2e/plan_build_test.go      |   2 +-
 backend/plugins/bamboo/e2e/plan_test.go            |   2 +-
 backend/plugins/bamboo/impl/impl.go                |  10 +-
 backend/plugins/bamboo/models/plan.go              |   3 +-
 backend/plugins/bamboo/models/task.go              |   9 +-
 backend/plugins/bamboo/tasks/shared.go             |   4 +-
 backend/plugins/bamboo/tasks/task_data.go          |   2 +-
 20 files changed, 291 insertions(+), 479 deletions(-)

diff --git a/backend/helpers/pluginhelper/api/api_collector_with_state.go b/backend/helpers/pluginhelper/api/api_collector_with_state.go
index 982906f01..e60b25033 100644
--- a/backend/helpers/pluginhelper/api/api_collector_with_state.go
+++ b/backend/helpers/pluginhelper/api/api_collector_with_state.go
@@ -43,7 +43,7 @@ type ApiCollectorStateManager struct {
 }
 
 type CollectorOptions struct {
-	TimeAfter string `json:"timeAfter,omitempty" mapstructure:"timeAfter"`
+	TimeAfter string `json:"timeAfter,omitempty" mapstructure:"timeAfter,omitempty"`
 }
 
 // NewStatefulApiCollector create a new ApiCollectorStateManager
diff --git a/backend/plugins/bamboo/api/blueprint_V200_test.go b/backend/plugins/bamboo/api/blueprint_V200_test.go
deleted file mode 100644
index 8d6b0eaf6..000000000
--- a/backend/plugins/bamboo/api/blueprint_V200_test.go
+++ /dev/null
@@ -1,187 +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 api
-
-import (
-	"database/sql"
-	"github.com/apache/incubator-devlake/core/dal"
-	"gorm.io/gorm/migrator"
-	"testing"
-
-	coreModels "github.com/apache/incubator-devlake/core/models"
-	mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
-	mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
-
-	"github.com/apache/incubator-devlake/core/errors"
-	"github.com/apache/incubator-devlake/core/models/common"
-	"github.com/apache/incubator-devlake/core/models/domainlayer/devops"
-	"github.com/apache/incubator-devlake/core/plugin"
-	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
-	"github.com/apache/incubator-devlake/helpers/unithelper"
-	"github.com/apache/incubator-devlake/plugins/bamboo/models"
-	"github.com/apache/incubator-devlake/plugins/bamboo/tasks"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/mock"
-)
-
-func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
-	const testConnectionID uint64 = 1
-	const testScopeConfigId uint64 = 2
-	const testKey string = "TEST"
-	const testBambooEndPoint string = "http://mail.nddtf.com:8085/rest/api/latest/"
-	const testLink string = "http://mail.nddtf.com:8085/rest/api/latest/plan/TEST"
-	const testUser string = "username"
-	const testPass string = "password"
-	const testName string = "bamboo-test"
-	const testScopeConfigName string = "bamboo scope config"
-	const testProxy string = ""
-
-	bpScopes := []*coreModels.BlueprintScope{
-		{
-			ScopeId: testKey,
-		},
-	}
-
-	var testBambooPlan = &models.BambooPlan{
-		Scope: common.Scope{
-			ConnectionId:  testConnectionID,
-			ScopeConfigId: testScopeConfigId,
-		},
-		PlanKey: testKey,
-		Name:    testName,
-		Href:    testLink,
-	}
-
-	var testScopeConfig = &models.BambooScopeConfig{
-		ScopeConfig: common.ScopeConfig{
-			Model: common.Model{
-				ID: testScopeConfigId,
-			},
-			Entities: []string{plugin.DOMAIN_TYPE_CICD},
-			Name:     testScopeConfigName,
-		},
-	}
-
-	var testColumTypes = []dal.ColumnMeta{
-		migrator.ColumnType{
-			NameValue: sql.NullString{
-				String: "abc",
-				Valid:  true,
-			},
-		},
-	}
-
-	var testBambooConnection = &models.BambooConnection{
-		BaseConnection: helper.BaseConnection{
-			Name: testName,
-			Model: common.Model{
-				ID: testConnectionID,
-			},
-		},
-		BambooConn: models.BambooConn{
-			RestConnection: helper.RestConnection{
-				Endpoint:         testBambooEndPoint,
-				Proxy:            testProxy,
-				RateLimitPerHour: 0,
-			},
-			BasicAuth: helper.BasicAuth{
-				Username: testUser,
-				Password: testPass,
-			},
-		},
-	}
-
-	var expectRepoId = "bamboo:BambooPlan:1:TEST"
-
-	var testSubTaskMeta = []plugin.SubTaskMeta{
-		tasks.ConvertPlansMeta,
-	}
-
-	var expectPlans = coreModels.PipelinePlan{
-		{
-			{
-				Plugin: "bamboo",
-				Subtasks: []string{
-					tasks.ConvertPlansMeta.Name,
-				},
-				Options: map[string]interface{}{
-					"connectionId":  uint64(1),
-					"planKey":       testKey,
-					"scopeConfigId": testScopeConfigId,
-				},
-			},
-		},
-	}
-
-	expectCicdScope := devops.NewCicdScope(expectRepoId, testName)
-	expectCicdScope.Description = ""
-	expectCicdScope.Url = ""
-
-	var err errors.Error
-
-	// register bamboo plugin for NewDomainIdGenerator
-	mockMeta := mockplugin.NewPluginMeta(t)
-	mockMeta.On("Name").Return("bamboo").Maybe()
-	mockMeta.On("RootPkgPath").Return("github.com/apache/incubator-devlake/plugins/bamboo")
-	err = plugin.RegisterPlugin("bamboo", mockMeta)
-	assert.Equal(t, err, nil)
-
-	// Refresh Global Variables and set the sql mock
-	basicRes = unithelper.DummyBasicRes(func(mockDal *mockdal.Dal) {
-		mockDal.On("First", mock.AnythingOfType("*models.BambooConnection"), mock.Anything).Run(func(args mock.Arguments) {
-			dst := args.Get(0).(*models.BambooConnection)
-			*dst = *testBambooConnection
-		}).Return(nil)
-
-		mockDal.On("First", mock.AnythingOfType("*models.BambooPlan"), mock.Anything).Run(func(args mock.Arguments) {
-			dst := args.Get(0).(*models.BambooPlan)
-			*dst = *testBambooPlan
-		}).Return(nil)
-
-		mockDal.On("First", mock.AnythingOfType("*models.BambooScopeConfig"), mock.Anything).Run(func(args mock.Arguments) {
-			dst := args.Get(0).(*models.BambooScopeConfig)
-			*dst = *testScopeConfig
-		}).Return(nil)
-		mockDal.On("GetPrimarykeyColumns", mock.AnythingOfType("*models.BambooConnection"), mock.Anything).Run(nil).Return(
-			testColumTypes, nil)
-		mockDal.On("GetColumns", mock.AnythingOfType("models.BambooConnection"), mock.Anything).Run(nil).Return(
-			testColumTypes, nil)
-		mockDal.On("GetColumns", mock.AnythingOfType("models.BambooPlan"), mock.Anything).Run(nil).Return(
-			testColumTypes, nil)
-		mockDal.On("GetColumns", mock.AnythingOfType("models.BambooScopeConfig"), mock.Anything).Run(nil).Return(
-			testColumTypes, nil)
-	})
-	Init(basicRes, mockMeta)
-
-	plans, scopes, err := MakePipelinePlanV200(testSubTaskMeta, testConnectionID, bpScopes)
-	assert.Equal(t, err, nil)
-
-	assert.Equal(t, expectPlans, plans)
-
-	// ignore CreatedDate UpdatedDate  CreatedAt UpdatedAt checking
-	expectCicdScope.CreatedDate = scopes[0].(*devops.CicdScope).CreatedDate
-	expectCicdScope.UpdatedDate = scopes[0].(*devops.CicdScope).UpdatedDate
-	expectCicdScope.CreatedAt = scopes[0].(*devops.CicdScope).CreatedAt
-	expectCicdScope.UpdatedAt = scopes[0].(*devops.CicdScope).UpdatedAt
-
-	var expectScopes = []plugin.Scope{
-		expectCicdScope,
-	}
-
-	assert.Equal(t, expectScopes, scopes)
-}
diff --git a/backend/plugins/bamboo/api/blueprint_v200.go b/backend/plugins/bamboo/api/blueprint_v200.go
index 1b252a2eb..ca7ae2bec 100644
--- a/backend/plugins/bamboo/api/blueprint_v200.go
+++ b/backend/plugins/bamboo/api/blueprint_v200.go
@@ -18,8 +18,6 @@ limitations under the License.
 package api
 
 import (
-	"fmt"
-
 	coreModels "github.com/apache/incubator-devlake/core/models"
 	"github.com/apache/incubator-devlake/plugins/bamboo/models"
 
@@ -30,90 +28,86 @@ import (
 	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
 	plugin "github.com/apache/incubator-devlake/core/plugin"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/helpers/srvhelper"
 )
 
 func MakePipelinePlanV200(
 	subtaskMetas []plugin.SubTaskMeta,
 	connectionId uint64,
-	scope []*coreModels.BlueprintScope,
+	bpScopes []*coreModels.BlueprintScope,
 ) (coreModels.PipelinePlan, []plugin.Scope, errors.Error) {
-	var err errors.Error
-	connection := new(models.BambooConnection)
-	err1 := connectionHelper.FirstById(connection, connectionId)
-	if err1 != nil {
-		return nil, nil, errors.Default.Wrap(err1, fmt.Sprintf("error on get connection by id[%d]", connectionId))
+	// load connection, scope and scopeConfig from the db
+	connection, err := dsHelper.ConnSrv.FindByPk(connectionId)
+	if err != nil {
+		return nil, nil, err
 	}
-
-	sc, err := makeScopeV200(connectionId, scope)
+	scopeDetails, err := dsHelper.ScopeSrv.MapScopeDetails(connectionId, bpScopes)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	pp, err := makePipelinePlanV200(subtaskMetas, scope, connection)
+	plan, err := makePipelinePlanV200(subtaskMetas, scopeDetails, connection)
+	if err != nil {
+		return nil, nil, err
+	}
+	scopes, err := makeScopesV200(scopeDetails, connection)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	return pp, sc, nil
+	return plan, scopes, nil
 }
 
-func makeScopeV200(connectionId uint64, scopes []*coreModels.BlueprintScope) ([]plugin.Scope, errors.Error) {
-	sc := make([]plugin.Scope, 0, len(scopes))
-
-	for _, scope := range scopes {
-		id := didgen.NewDomainIdGenerator(&models.BambooPlan{}).Generate(connectionId, scope.ScopeId)
+func makePipelinePlanV200(
+	subtaskMetas []plugin.SubTaskMeta,
+	scopeDetails []*srvhelper.ScopeDetail[models.BambooPlan, models.BambooScopeConfig],
+	connection *models.BambooConnection,
+) (coreModels.PipelinePlan, errors.Error) {
+	plan := make(coreModels.PipelinePlan, len(scopeDetails))
+	for i, scopeDetail := range scopeDetails {
+		stage := plan[i]
+		if stage == nil {
+			stage = coreModels.PipelineStage{}
+		}
 
-		// get project from db
-		project, scopeConfig, err := scopeHelper.DbHelper().GetScopeAndConfig(connectionId, scope.ScopeId)
+		scope, scopeConfig := scopeDetail.Scope, scopeDetail.ScopeConfig
+		// construct task options for Jira
+		task, err := helper.MakePipelinePlanTask(
+			"bamboo",
+			subtaskMetas,
+			scopeConfig.Entities,
+			models.BambooOptions{
+				ConnectionId: scope.ConnectionId,
+				PlanKey:      scope.PlanKey,
+			},
+		)
 		if err != nil {
 			return nil, err
 		}
 
-		// add cicd_scope to scopes
-		if utils.StringsContains(scopeConfig.Entities, plugin.DOMAIN_TYPE_CICD) {
-			scopeCICD := devops.NewCicdScope(id, project.Name)
-
-			sc = append(sc, scopeCICD)
-		}
+		stage = append(stage, task)
+		plan[i] = stage
 	}
 
-	return sc, nil
+	return plan, nil
 }
 
-func makePipelinePlanV200(
-	subtaskMetas []plugin.SubTaskMeta,
-	scopes []*coreModels.BlueprintScope,
+func makeScopesV200(
+	scopeDetails []*srvhelper.ScopeDetail[models.BambooPlan, models.BambooScopeConfig],
 	connection *models.BambooConnection,
-) (coreModels.PipelinePlan, errors.Error) {
-	plans := make(coreModels.PipelinePlan, 0, len(scopes))
-	for _, scope := range scopes {
-		var stage coreModels.PipelineStage
-		var err errors.Error
-		// get project
-		_, scopeConfig, err := scopeHelper.DbHelper().GetScopeAndConfig(connection.ID, scope.ScopeId)
-		if err != nil {
-			return nil, err
-		}
+) ([]plugin.Scope, errors.Error) {
+	scopes := make([]plugin.Scope, 0, len(scopeDetails))
 
-		// bamboo main part
-		options := make(map[string]interface{})
-		options["connectionId"] = connection.ID
-		options["planKey"] = scope.ScopeId
-		options["scopeConfigId"] = scopeConfig.ID
+	idgen := didgen.NewDomainIdGenerator(&models.BambooPlan{})
+	for _, scopeDetail := range scopeDetails {
+		scope, scopeConfig := scopeDetail.Scope, scopeDetail.ScopeConfig
+		id := idgen.Generate(connection.ID, scope.PlanKey)
 
-		// construct subtasks
-		subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, scopeConfig.Entities)
-		if err != nil {
-			return nil, err
+		// add cicd_scope to scopes
+		if utils.StringsContains(scopeConfig.Entities, plugin.DOMAIN_TYPE_CICD) {
+			scopes = append(scopes, devops.NewCicdScope(id, scope.PlanKey))
 		}
-
-		stage = append(stage, &coreModels.PipelineTask{
-			Plugin:   "bamboo",
-			Subtasks: subtasks,
-			Options:  options,
-		})
-
-		plans = append(plans, stage)
 	}
-	return plans, nil
+
+	return scopes, nil
 }
diff --git a/backend/plugins/bamboo/api/connection.go b/backend/plugins/bamboo/api/connection_api.go
similarity index 79%
rename from backend/plugins/bamboo/api/connection.go
rename to backend/plugins/bamboo/api/connection_api.go
index 6346c8b69..1c1bb0982 100644
--- a/backend/plugins/bamboo/api/connection.go
+++ b/backend/plugins/bamboo/api/connection_api.go
@@ -86,10 +86,9 @@ func TestConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/bamboo/{connectionId}/test [POST]
 func TestExistingConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	connection := &models.BambooConnection{}
-	err := connectionHelper.First(connection, input.Params)
+	connection, err := dsHelper.ConnApi.FindByPk(input)
 	if err != nil {
-		return nil, errors.BadInput.Wrap(err, "find connection from db")
+		return nil, err
 	}
 	if err := api.DecodeMapStruct(input.Body, connection, false); err != nil {
 		return nil, err
@@ -111,13 +110,7 @@ func TestExistingConnection(input *plugin.ApiResourceInput) (*plugin.ApiResource
 // @Failure 500  {string} errcode.Error "Internel Error"
 // @Router /plugins/bamboo/connections [POST]
 func PostConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	// update from request and save to database
-	connection := &models.BambooConnection{}
-	err := connectionHelper.Create(connection, input)
-	if err != nil {
-		return nil, err
-	}
-	return &plugin.ApiResourceOutput{Body: connection.Sanitize(), Status: http.StatusOK}, nil
+	return dsHelper.ConnApi.Post(input)
 }
 
 // @Summary patch bamboo connection
@@ -130,17 +123,7 @@ func PostConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internel Error"
 // @Router /plugins/bamboo/connections/{connectionId} [PATCH]
 func PatchConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	connection := &models.BambooConnection{}
-	if err := connectionHelper.First(&connection, input.Params); err != nil {
-		return nil, err
-	}
-	if err := (&models.BambooConnection{}).MergeFromRequest(connection, input.Body); err != nil {
-		return nil, errors.Convert(err)
-	}
-	if err := connectionHelper.SaveWithCreateOrUpdate(connection); err != nil {
-		return nil, err
-	}
-	return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, nil
+	return dsHelper.ConnApi.Patch(input)
 }
 
 // @Summary delete a bamboo connection
@@ -153,13 +136,7 @@ func PatchConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internel Error"
 // @Router /plugins/bamboo/connections/{connectionId} [DELETE]
 func DeleteConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	conn := &models.BambooConnection{}
-	output, err := connectionHelper.Delete(conn, input)
-	if err != nil {
-		return output, err
-	}
-	output.Body = conn.Sanitize()
-	return output, nil
+	return dsHelper.ConnApi.Delete(input)
 }
 
 // @Summary get all bamboo connections
@@ -170,15 +147,7 @@ func DeleteConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput
 // @Failure 500  {string} errcode.Error "Internel Error"
 // @Router /plugins/bamboo/connections [GET]
 func ListConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	var connections []models.BambooConnection
-	err := connectionHelper.List(&connections)
-	if err != nil {
-		return nil, err
-	}
-	for idx, c := range connections {
-		connections[idx] = c.Sanitize()
-	}
-	return &plugin.ApiResourceOutput{Body: connections, Status: http.StatusOK}, nil
+	return dsHelper.ConnApi.GetAll(input)
 }
 
 // @Summary get bamboo connection detail
@@ -190,7 +159,5 @@ func ListConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internel Error"
 // @Router /plugins/bamboo/connections/{connectionId} [GET]
 func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	connection := &models.BambooConnection{}
-	err := connectionHelper.First(connection, input.Params)
-	return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, err
+	return dsHelper.ConnApi.GetDetail(input)
 }
diff --git a/backend/plugins/bamboo/api/remote.go b/backend/plugins/bamboo/api/remote.go
deleted file mode 100644
index ea1649e22..000000000
--- a/backend/plugins/bamboo/api/remote.go
+++ /dev/null
@@ -1,145 +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 api
-
-import (
-	gocontext "context"
-	"fmt"
-	"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/bamboo/models"
-	"net/url"
-)
-
-// RemoteScopes list all available scope for users
-// @Summary list all available scope for users
-// @Description list all available scope for users
-// @Tags plugins/bamboo
-// @Accept application/json
-// @Param connectionId path int false "connection ID"
-// @Param groupId query string false "group ID"
-// @Param pageToken query string false "page Token"
-// @Success 200  {object} api.RemoteScopesOutput
-// @Failure 400  {object} shared.ApiBody "Bad Request"
-// @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/connections/{connectionId}/remote-scopes [GET]
-func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return remoteHelper.GetScopesFromRemote(input, nil, getRemotePlans)
-}
-
-// SearchRemoteScopes use the Search API and only return project
-// @Summary use the Search API and only return project
-// @Description use the Search API and only return project
-// @Tags plugins/bamboo
-// @Accept application/json
-// @Param connectionId path int false "connection ID"
-// @Param search query string false "search"
-// @Param page query int false "page number"
-// @Param pageSize query int false "page size per page"
-// @Success 200  {object} api.SearchRemoteScopesOutput
-// @Failure 400  {object} shared.ApiBody "Bad Request"
-// @Failure 500  {object} shared.ApiBody "Internal Error"
-// @Router /plugins/bamboo/connections/{connectionId}/search-remote-scopes [GET]
-func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return remoteHelper.SearchRemoteScopes(input,
-		func(basicRes context.BasicRes, queryData *api.RemoteQueryData, connection models.BambooConnection) ([]models.ApiBambooPlan, errors.Error) {
-			apiClient, err := api.NewApiClientFromConnection(gocontext.TODO(), basicRes, &connection)
-			if err != nil {
-				return nil, errors.BadInput.Wrap(err, "failed to get create apiClient")
-			}
-			query := initialQuery(queryData)
-			if len(queryData.Search) == 0 {
-				return nil, errors.BadInput.New("empty search query")
-			}
-			query.Set("searchTerm", queryData.Search[0])
-			// request search
-			res, err := apiClient.Get("search/plans.json", query, nil)
-			if err != nil {
-				return nil, err
-			}
-
-			resBody := models.ApiBambooSearchPlanResponse{}
-			err = api.UnmarshalResponse(res, &resBody)
-			if err != nil {
-				return nil, err
-			}
-
-			var apiBambooPlans []models.ApiBambooPlan
-			// append project to output
-			for _, apiResult := range resBody.SearchResults {
-				bambooPlan := models.ApiBambooPlan{
-					Key:  apiResult.SearchEntity.Key,
-					Name: apiResult.SearchEntity.Name(),
-				}
-				apiBambooPlans = append(apiBambooPlans, bambooPlan)
-			}
-			return apiBambooPlans, err
-		})
-}
-
-func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	connection := &models.BambooConnection{}
-	err := connectionHelper.First(connection, input.Params)
-	if err != nil {
-		return nil, err
-	}
-	return remoteHelper.ProxyApiGet(connection, input.Params["path"], input.Query)
-}
-
-func initialQuery(queryData *api.RemoteQueryData) url.Values {
-	query := url.Values{}
-	query.Set("showEmpty", fmt.Sprintf("%v", false))
-	query.Set("max-result", fmt.Sprintf("%v", queryData.PerPage))
-	query.Set("start-index", fmt.Sprintf("%v", (queryData.Page-1)*queryData.PerPage))
-	return query
-}
-
-func getRemotePlans(basicRes context.BasicRes, gid string, queryData *api.RemoteQueryData, connection models.BambooConnection) ([]models.ApiBambooPlan, errors.Error) {
-	query := initialQuery(queryData)
-	// create api client
-	apiClient, err := api.NewApiClientFromConnection(gocontext.TODO(), basicRes, &connection)
-	if err != nil {
-		return nil, err
-	}
-	res, err := apiClient.Get("plan.json", query, nil)
-
-	if err != nil {
-		return nil, err
-	}
-	var planRes struct {
-		Expand string `json:"expand"`
-		Link   struct {
-			Href string `json:"href"`
-			Rel  string `json:"rel"`
-		} `json:"link"`
-		Plans struct {
-			Size       int                    `json:"size"`
-			Expand     string                 `json:"expand"`
-			StartIndex int                    `json:"start-index"`
-			MaxResult  int                    `json:"max-result"`
-			Plan       []models.ApiBambooPlan `json:"plan"`
-		} `json:"plans"`
-	}
-	err = api.UnmarshalResponse(res, &planRes)
-	if err != nil {
-		return nil, err
-	}
-	return planRes.Plans.Plan, err
-}
diff --git a/backend/plugins/bamboo/api/remote_api.go b/backend/plugins/bamboo/api/remote_api.go
new file mode 100644
index 000000000..e75d0ad51
--- /dev/null
+++ b/backend/plugins/bamboo/api/remote_api.go
@@ -0,0 +1,182 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"net/url"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	dsmodels "github.com/apache/incubator-devlake/helpers/pluginhelper/api/models"
+	"github.com/apache/incubator-devlake/plugins/bamboo/models"
+)
+
+type BambooRemotePagination struct {
+	MaxResult  int `json:"max-result" validate:"required"`
+	StartIndex int `json:"start-index" validate:"required"`
+}
+
+func listBambooRemoteScopes(
+	connection *models.BambooConnection,
+	apiClient plugin.ApiClient,
+	groupId string,
+	page BambooRemotePagination,
+) (
+	children []dsmodels.DsRemoteApiScopeListEntry[models.BambooPlan],
+	nextPage *BambooRemotePagination,
+	err errors.Error,
+) {
+	if page.MaxResult == 0 {
+		page.MaxResult = 100
+	}
+
+	query := url.Values{
+		"showEmpty":   []string{"false"},
+		"max-result":  []string{fmt.Sprintf("%v", page.MaxResult)},
+		"start-index": []string{fmt.Sprintf("%v", page.StartIndex)},
+	}
+	res, err := apiClient.Get("plan.json", query, nil)
+
+	if err != nil {
+		return
+	}
+	var planRes struct {
+		Expand string `json:"expand"`
+		Link   struct {
+			Href string `json:"href"`
+			Rel  string `json:"rel"`
+		} `json:"link"`
+		Plans struct {
+			Size       int                    `json:"size"`
+			Expand     string                 `json:"expand"`
+			StartIndex int                    `json:"start-index"`
+			MaxResult  int                    `json:"max-result"`
+			Plan       []models.ApiBambooPlan `json:"plan"`
+		} `json:"plans"`
+	}
+	err = api.UnmarshalResponse(res, &planRes)
+	if err != nil {
+		return
+	}
+	for _, plan := range planRes.Plans.Plan {
+		children = append(children, toPlanModel(&plan))
+	}
+	// there may be more repos
+	if len(children) == page.MaxResult {
+		nextPage = &BambooRemotePagination{
+			MaxResult:  page.MaxResult,
+			StartIndex: page.StartIndex + page.MaxResult,
+		}
+	}
+	return
+}
+
+func searchBambooPlans(
+	apiClient plugin.ApiClient,
+	params *dsmodels.DsRemoteApiScopeSearchParams,
+) (
+	children []dsmodels.DsRemoteApiScopeListEntry[models.BambooPlan],
+	err errors.Error,
+) {
+	res, err := apiClient.Get(
+		"search/plans.json",
+		url.Values{
+			"searchTerm": []string{params.Search},
+		},
+		nil,
+	)
+	if err != nil {
+		return nil, err
+	}
+	resBody := models.ApiBambooSearchPlanResponse{}
+	err = api.UnmarshalResponse(res, &resBody)
+	if err != nil {
+		return nil, err
+	}
+	for _, r := range resBody.SearchResults {
+		children = append(children, toPlanModel(&models.ApiBambooPlan{
+			Key:  r.SearchEntity.Key,
+			Name: r.SearchEntity.Name(),
+		}))
+	}
+	return
+}
+
+func toPlanModel(plan *models.ApiBambooPlan) dsmodels.DsRemoteApiScopeListEntry[models.BambooPlan] {
+	return dsmodels.DsRemoteApiScopeListEntry[models.BambooPlan]{
+		Type:     api.RAS_ENTRY_TYPE_SCOPE,
+		Id:       plan.Key,
+		Name:     plan.Name,
+		FullName: plan.Name,
+		Data: &models.BambooPlan{
+			PlanKey:   plan.Key,
+			Name:      plan.Name,
+			ShortName: plan.ShortName,
+			ShortKey:  plan.ShortKey,
+			Type:      plan.Type,
+			Enabled:   plan.Enabled,
+			Href:      plan.Link.Href,
+			Rel:       plan.Link.Rel,
+		},
+	}
+}
+
+// RemoteScopes list all available scopes on the remote server
+// @Summary list all available scopes on the remote server
+// @Description list all available scopes on the remote server
+// @Accept application/json
+// @Param connectionId path int false "connection ID"
+// @Param groupId query string false "group ID"
+// @Param pageToken query string false "page Token"
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Success 200  {object} dsmodels.DsRemoteApiScopeList[models.GithubRepo]
+// @Tags plugins/bamboo
+// @Router /plugins/bamboo/connections/{connectionId}/remote-scopes [GET]
+func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	return raScopeList.Get(input)
+}
+
+// SearchRemoteScopes searches scopes on the remote server
+// @Summary searches scopes on the remote server
+// @Description searches scopes on the remote server
+// @Accept application/json
+// @Param connectionId path int false "connection ID"
+// @Param search query string false "search"
+// @Param page query int false "page number"
+// @Param pageSize query int false "page size per page"
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Success 200  {object} dsmodels.DsRemoteApiScopeList[models.GithubRepo] "the parentIds are always null"
+// @Tags plugins/bamboo
+// @Router /plugins/bamboo/connections/{connectionId}/search-remote-scopes [GET]
+func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	return raScopeSearch.Get(input)
+}
+
+// @Summary Remote server API proxy
+// @Description Forward API requests to the specified remote server
+// @Param connectionId path int true "connection ID"
+// @Param path path string true "path to a API endpoint"
+// @Tags plugins/bamboo
+// @Router /plugins/bamboo/connections/{connectionId}/proxy/{path} [GET]
+func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	return raProxy.Proxy(input)
+}
diff --git a/backend/plugins/bamboo/api/scope.go b/backend/plugins/bamboo/api/scope_api.go
similarity index 82%
rename from backend/plugins/bamboo/api/scope.go
rename to backend/plugins/bamboo/api/scope_api.go
index f71fbfdf7..0e4c7961c 100644
--- a/backend/plugins/bamboo/api/scope.go
+++ b/backend/plugins/bamboo/api/scope_api.go
@@ -20,30 +20,29 @@ package api
 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/bamboo/models"
 )
 
-// nolint
-type scopeReq struct {
-	Data []models.BambooPlan `json:"data"`
-}
+type PutScopesReqBody api.PutScopesReqBody[models.BambooPlan]
+type ScopeDetail api.ScopeDetail[models.BambooPlan, models.BambooScopeConfig]
 
-// PutScope create or update bamboo plan
+// PutScopes create or update bamboo plan
 // @Summary create or update bamboo plan
 // @Description Create or update bamboo plan
 // @Tags plugins/bamboo
 // @Accept application/json
 // @Param connectionId path int true "connection ID"
-// @Param scope body scopeReq true "json"
-// @Success 200  {object} models.BambooPlan
+// @Param scope body PutScopesReqBody true "json"
+// @Success 200  {object} []models.BambooPlan
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes [PUT]
-func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeHelper.Put(input)
+func PutScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	return dsHelper.ScopeApi.PutMultiple(input)
 }
 
-// UpdateScope patch to bamboo plan
+// PatchScope patch to bamboo plan
 // @Summary patch to bamboo plan
 // @Description patch to bamboo plan
 // @Tags plugins/bamboo
@@ -55,23 +54,23 @@ func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [PATCH]
-func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeHelper.Update(input)
+func PatchScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	return dsHelper.ScopeApi.Patch(input)
 }
 
-// GetScopeList get Bamboo plans
+// GetScopes get Bamboo plans
 // @Summary get Bamboo plans
 // @Description get Bamboo plans
 // @Tags plugins/bamboo
 // @Param connectionId path int true "connection ID"
 // @Param searchTerm query string false "search term for scope name"
 // @Param blueprints query bool false "also return blueprints using these scopes as part of the payload"
-// @Success 200  {object} []models.BambooPlan
+// @Success 200  {object} []ScopeDetail
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/ [GET]
 func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeHelper.GetScopeList(input)
+	return dsHelper.ScopeApi.GetPage(input)
 }
 
 // GetScope get one Bamboo plan
@@ -82,12 +81,12 @@ func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, er
 // @Param scopeId path int true "plan key"
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
-// @Success 200  {object} models.BambooPlan
+// @Success 200  {object} ScopeDetail
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [GET]
 func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeHelper.GetScope(input)
+	return dsHelper.ScopeApi.GetScopeDetail(input)
 }
 
 // DeleteScope delete plugin data associated with the scope and optionally the scope itself
@@ -103,5 +102,5 @@ func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scopes/{scopeId} [DELETE]
 func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeHelper.Delete(input)
+	return dsHelper.ScopeApi.Delete(input)
 }
diff --git a/backend/plugins/bamboo/api/scope_config.go b/backend/plugins/bamboo/api/scope_config_api.go
similarity index 91%
rename from backend/plugins/bamboo/api/scope_config.go
rename to backend/plugins/bamboo/api/scope_config_api.go
index 01921146d..2cc545645 100644
--- a/backend/plugins/bamboo/api/scope_config.go
+++ b/backend/plugins/bamboo/api/scope_config_api.go
@@ -34,10 +34,10 @@ import (
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scope-configs [POST]
 func CreateScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeConfigHelper.Create(input)
+	return dsHelper.ScopeConfigApi.Post(input)
 }
 
-// UpdateScopeConfig update scope config for Bamboo
+// PatchScopeConfig update scope config for Bamboo
 // @Summary update scope config for Bamboo
 // @Description update scope config for Bamboo
 // @Tags plugins/bamboo
@@ -49,8 +49,8 @@ func CreateScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutpu
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scope-configs/{id} [PATCH]
-func UpdateScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeConfigHelper.Update(input)
+func PatchScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	return dsHelper.ScopeConfigApi.Patch(input)
 }
 
 // GetScopeConfig return onescope config
@@ -64,7 +64,7 @@ func UpdateScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutpu
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scope-configs/{id} [GET]
 func GetScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeConfigHelper.Get(input)
+	return dsHelper.ScopeConfigApi.GetDetail(input)
 }
 
 // GetScopeConfigList return all scope configs
@@ -79,7 +79,7 @@ func GetScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput,
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scope-configs [GET]
 func GetScopeConfigList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeConfigHelper.List(input)
+	return dsHelper.ScopeConfigApi.GetAll(input)
 }
 
 // DeleteScopeConfig delete a scope config
@@ -93,5 +93,5 @@ func GetScopeConfigList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutp
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/bamboo/connections/{connectionId}/scope-configs/{id} [DELETE]
 func DeleteScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
-	return scopeConfigHelper.Delete(input)
+	return dsHelper.ScopeConfigApi.Delete(input)
 }
diff --git a/backend/plugins/bamboo/e2e/deploy_build_test.go b/backend/plugins/bamboo/e2e/deploy_build_test.go
index ef40e1e90..c89ab3058 100644
--- a/backend/plugins/bamboo/e2e/deploy_build_test.go
+++ b/backend/plugins/bamboo/e2e/deploy_build_test.go
@@ -18,6 +18,9 @@ limitations under the License.
 package e2e
 
 import (
+	"testing"
+	"time"
+
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/devops"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
@@ -25,8 +28,6 @@ import (
 	"github.com/apache/incubator-devlake/plugins/bamboo/impl"
 	"github.com/apache/incubator-devlake/plugins/bamboo/models"
 	"github.com/apache/incubator-devlake/plugins/bamboo/tasks"
-	"testing"
-	"time"
 )
 
 func getFakeAPIClient() *helper.ApiAsyncClient {
@@ -40,7 +41,7 @@ func getFakeAPIClient() *helper.ApiAsyncClient {
 func TestBambooDeployBuildDataFlow(t *testing.T) {
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId: 1,
 			PlanKey:      "TEST-PLA2",
diff --git a/backend/plugins/bamboo/e2e/deploy_test.go b/backend/plugins/bamboo/e2e/deploy_test.go
index 54bf4a4a7..49e1ae304 100644
--- a/backend/plugins/bamboo/e2e/deploy_test.go
+++ b/backend/plugins/bamboo/e2e/deploy_test.go
@@ -29,7 +29,7 @@ import (
 func TestBambooDeployDataFlow(t *testing.T) {
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId:      1,
 			PlanKey:           "TEST1",
diff --git a/backend/plugins/bamboo/e2e/job_build_test.go b/backend/plugins/bamboo/e2e/job_build_test.go
index 4be58937c..7ed7709bc 100644
--- a/backend/plugins/bamboo/e2e/job_build_test.go
+++ b/backend/plugins/bamboo/e2e/job_build_test.go
@@ -18,6 +18,8 @@ limitations under the License.
 package e2e
 
 import (
+	"testing"
+
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/devops"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
@@ -25,14 +27,13 @@ import (
 	"github.com/apache/incubator-devlake/plugins/bamboo/impl"
 	"github.com/apache/incubator-devlake/plugins/bamboo/models"
 	"github.com/apache/incubator-devlake/plugins/bamboo/tasks"
-	"testing"
 )
 
 func TestBambooJobBuildDataFlow(t *testing.T) {
 
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId: 1,
 			PlanKey:      "TEST-PLA3",
diff --git a/backend/plugins/bamboo/e2e/job_test.go b/backend/plugins/bamboo/e2e/job_test.go
index dc89148e7..8d3a29b0b 100644
--- a/backend/plugins/bamboo/e2e/job_test.go
+++ b/backend/plugins/bamboo/e2e/job_test.go
@@ -31,7 +31,7 @@ func TestBambooJobDataFlow(t *testing.T) {
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
 
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId: 1,
 			PlanKey:      "TEST-PLA1",
diff --git a/backend/plugins/bamboo/e2e/plan_build_commits_test.go b/backend/plugins/bamboo/e2e/plan_build_commits_test.go
index a9d264e2f..edbe7f05c 100644
--- a/backend/plugins/bamboo/e2e/plan_build_commits_test.go
+++ b/backend/plugins/bamboo/e2e/plan_build_commits_test.go
@@ -33,7 +33,7 @@ func TestBambooPlanBuildCommitsDataFlow(t *testing.T) {
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
 
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId: 1,
 			PlanKey:      "TEST-PLA2",
diff --git a/backend/plugins/bamboo/e2e/plan_build_test.go b/backend/plugins/bamboo/e2e/plan_build_test.go
index ca9755d35..7c3ea69ec 100644
--- a/backend/plugins/bamboo/e2e/plan_build_test.go
+++ b/backend/plugins/bamboo/e2e/plan_build_test.go
@@ -34,7 +34,7 @@ func TestBambooPlanBuildDataFlow(t *testing.T) {
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
 
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId: 1,
 			PlanKey:      "TEST-PLA2",
diff --git a/backend/plugins/bamboo/e2e/plan_test.go b/backend/plugins/bamboo/e2e/plan_test.go
index 09987c8b1..dd56670a6 100644
--- a/backend/plugins/bamboo/e2e/plan_test.go
+++ b/backend/plugins/bamboo/e2e/plan_test.go
@@ -33,7 +33,7 @@ func TestBambooPlanDataFlow(t *testing.T) {
 	var bamboo impl.Bamboo
 	dataflowTester := e2ehelper.NewDataFlowTester(t, "bamboo", bamboo)
 
-	taskData := &tasks.BambooTaskData{
+	taskData := &tasks.BambooOptions{
 		Options: &models.BambooOptions{
 			ConnectionId:      3,
 			PlanKey:           "TEST1",
diff --git a/backend/plugins/bamboo/impl/impl.go b/backend/plugins/bamboo/impl/impl.go
index 382f6e4fa..ddec6bd6c 100644
--- a/backend/plugins/bamboo/impl/impl.go
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -185,7 +185,7 @@ func (p Bamboo) PrepareTaskData(taskCtx plugin.TaskContext, options map[string]i
 	if err := regexEnricher.TryAdd(devops.ENV_NAME_PATTERN, op.EnvNamePattern); err != nil {
 		return nil, errors.BadInput.Wrap(err, "invalid value for `envNamePattern`")
 	}
-	return &tasks.BambooTaskData{
+	return &tasks.BambooOptions{
 		Options:       op,
 		ApiClient:     apiClient,
 		RegexEnricher: regexEnricher,
@@ -223,13 +223,13 @@ func (p Bamboo) ApiResources() map[string]map[string]plugin.ApiResourceHandler {
 			"GET":  api.GetScopeConfigList,
 		},
 		"connections/:connectionId/scope-configs/:id": {
-			"PATCH":  api.UpdateScopeConfig,
+			"PATCH":  api.PatchScopeConfig,
 			"GET":    api.GetScopeConfig,
 			"DELETE": api.DeleteScopeConfig,
 		},
 		"connections/:connectionId/scopes": {
 			"GET": api.GetScopeList,
-			"PUT": api.PutScope,
+			"PUT": api.PutScopes,
 		},
 		"connections/:connectionId/scopes/:scopeId/latest-sync-state": {
 			"GET": api.GetScopeLatestSyncState,
@@ -242,7 +242,7 @@ func (p Bamboo) ApiResources() map[string]map[string]plugin.ApiResourceHandler {
 		},
 		"connections/:connectionId/scopes/:scopeId": {
 			"GET":    api.GetScope,
-			"PATCH":  api.UpdateScope,
+			"PATCH":  api.PatchScope,
 			"DELETE": api.DeleteScope,
 		},
 		"connections/:connectionId/proxy/rest/*path": {
@@ -252,7 +252,7 @@ func (p Bamboo) ApiResources() map[string]map[string]plugin.ApiResourceHandler {
 }
 
 func (p Bamboo) Close(taskCtx plugin.TaskContext) errors.Error {
-	data, ok := taskCtx.GetData().(*tasks.BambooTaskData)
+	data, ok := taskCtx.GetData().(*tasks.BambooOptions)
 	if !ok {
 		return errors.Default.New(fmt.Sprintf("GetData failed when try to close %+v", taskCtx))
 	}
diff --git a/backend/plugins/bamboo/models/plan.go b/backend/plugins/bamboo/models/plan.go
index 26d0d3c1e..3811596f1 100644
--- a/backend/plugins/bamboo/models/plan.go
+++ b/backend/plugins/bamboo/models/plan.go
@@ -18,9 +18,10 @@ limitations under the License.
 package models
 
 import (
+	"strings"
+
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/plugin"
-	"strings"
 )
 
 var _ plugin.ToolLayerScope = (*BambooPlan)(nil)
diff --git a/backend/plugins/bamboo/models/task.go b/backend/plugins/bamboo/models/task.go
index ff2baf62f..72cdd95fb 100644
--- a/backend/plugins/bamboo/models/task.go
+++ b/backend/plugins/bamboo/models/task.go
@@ -29,10 +29,9 @@ type BambooOptions struct {
 	// options means some custom params required by plugin running.
 	// Such As How many rows do your want
 	// You can use it in sub tasks and you need pass it in main.go and pipelines.
-	ConnectionId         uint64   `json:"connectionId"`
-	PlanKey              string   `json:"planKey"`
-	Tasks                []string `json:"tasks,omitempty"`
-	ScopeConfigId        uint64   `mapstructure:"scopeConfigId" json:"scopeConfigId"`
-	*BambooScopeConfig   `mapstructure:"scopeConfig" json:"scopeConfig"`
+	ConnectionId         uint64 `json:"connectionId" mapstructure:"connectionId"`
+	PlanKey              string `json:"planKey" mapstructure:"planKey"`
+	ScopeConfigId        uint64 ` json:"scopeConfigId" mapstructure:"scopeConfigId,omitempty"`
+	*BambooScopeConfig   `json:"scopeConfig" mapstructure:"scopeConfig,omitempty"`
 	api.CollectorOptions `mapstructure:",squash"`
 }
diff --git a/backend/plugins/bamboo/tasks/shared.go b/backend/plugins/bamboo/tasks/shared.go
index 16b007ae4..182670b95 100644
--- a/backend/plugins/bamboo/tasks/shared.go
+++ b/backend/plugins/bamboo/tasks/shared.go
@@ -53,8 +53,8 @@ const (
 	ResultSuccessful = "Successful"
 )
 
-func CreateRawDataSubTaskArgs(taskCtx plugin.SubTaskContext, rawTable string) (*api.RawDataSubTaskArgs, *BambooTaskData) {
-	data := taskCtx.GetData().(*BambooTaskData)
+func CreateRawDataSubTaskArgs(taskCtx plugin.SubTaskContext, rawTable string) (*api.RawDataSubTaskArgs, *BambooOptions) {
+	data := taskCtx.GetData().(*BambooOptions)
 	filteredData := *data
 	filteredData.Options = &models.BambooOptions{}
 	*filteredData.Options = *data.Options
diff --git a/backend/plugins/bamboo/tasks/task_data.go b/backend/plugins/bamboo/tasks/task_data.go
index da8f05221..c5427ac1a 100644
--- a/backend/plugins/bamboo/tasks/task_data.go
+++ b/backend/plugins/bamboo/tasks/task_data.go
@@ -23,7 +23,7 @@ import (
 	"github.com/apache/incubator-devlake/plugins/bamboo/models"
 )
 
-type BambooTaskData struct {
+type BambooOptions struct {
 	Options       *models.BambooOptions
 	ApiClient     *helper.ApiAsyncClient
 	RegexEnricher *helper.RegexEnricher