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

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

commit f39f0d2216270a674738d641279d028ddd4033ff
Author: Klesh Wong <[email protected]>
AuthorDate: Tue Dec 5 15:39:12 2023 +0800

    refactor: jira adopts new dshelpers
---
 backend/plugins/jira/api/blueprint_v200.go         |  72 ++++++----
 backend/plugins/jira/api/blueprint_v200_test.go    | 110 ---------------
 .../jira/api/{connection.go => connection_api.go}  |  46 +-----
 backend/plugins/jira/api/init.go                   |  53 +++----
 backend/plugins/jira/api/proxy.go                  |  51 -------
 backend/plugins/jira/api/remote.go                 | 136 ------------------
 backend/plugins/jira/api/remote_api.go             | 154 +++++++++++++++++++++
 .../plugins/jira/api/{scope.go => scope_api.go}    |  24 ++--
 .../api/{scope_config.go => scope_config_api.go}   |  21 ++-
 backend/plugins/jira/impl/impl.go                  |  15 +-
 backend/plugins/jira/tasks/task_data.go            |   1 -
 11 files changed, 242 insertions(+), 441 deletions(-)

diff --git a/backend/plugins/jira/api/blueprint_v200.go 
b/backend/plugins/jira/api/blueprint_v200.go
index d67b86b98..e3859286b 100644
--- a/backend/plugins/jira/api/blueprint_v200.go
+++ b/backend/plugins/jira/api/blueprint_v200.go
@@ -18,6 +18,8 @@ limitations under the License.
 package api
 
 import (
+       "context"
+
        "github.com/apache/incubator-devlake/core/errors"
        coreModels "github.com/apache/incubator-devlake/core/models"
        "github.com/apache/incubator-devlake/core/models/domainlayer"
@@ -26,6 +28,7 @@ import (
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/core/utils"
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/helpers/srvhelper"
        "github.com/apache/incubator-devlake/plugins/jira/models"
 )
 
@@ -34,12 +37,28 @@ func MakeDataSourcePipelinePlanV200(
        connectionId uint64,
        bpScopes []*coreModels.BlueprintScope,
 ) (coreModels.PipelinePlan, []plugin.Scope, errors.Error) {
-       plan := make(coreModels.PipelinePlan, len(bpScopes))
-       plan, err := makeDataSourcePipelinePlanV200(subtaskMetas, plan, 
bpScopes, connectionId)
+       // load connection, scope and scopeConfig from the db
+       connection, err := dsHelper.ConnSrv.FindByPk(connectionId)
+       if err != nil {
+               return nil, nil, err
+       }
+       scopeDetails, err := dsHelper.ScopeSrv.MapScopeDetails(connectionId, 
bpScopes)
+       if err != nil {
+               return nil, nil, err
+       }
+
+       // needed for the connection to populate its access tokens
+       // if AppKey authentication method is selected
+       _, err = helper.NewApiClientFromConnection(context.TODO(), basicRes, 
connection)
+       if err != nil {
+               return nil, nil, err
+       }
+
+       plan, err := makeDataSourcePipelinePlanV200(subtaskMetas, scopeDetails, 
connection)
        if err != nil {
                return nil, nil, err
        }
-       scopes, err := makeScopesV200(bpScopes, connectionId)
+       scopes, err := makeScopesV200(scopeDetails, connection)
        if err != nil {
                return nil, nil, err
        }
@@ -49,35 +68,32 @@ func MakeDataSourcePipelinePlanV200(
 
 func makeDataSourcePipelinePlanV200(
        subtaskMetas []plugin.SubTaskMeta,
-       plan coreModels.PipelinePlan,
-       bpScopes []*coreModels.BlueprintScope,
-       connectionId uint64,
+       scopeDetails []*srvhelper.ScopeDetail[models.JiraBoard, 
models.JiraScopeConfig],
+       connection *models.JiraConnection,
 ) (coreModels.PipelinePlan, errors.Error) {
-       for i, bpScope := range bpScopes {
+       plan := make(coreModels.PipelinePlan, len(scopeDetails))
+       for i, scopeDetail := range scopeDetails {
                stage := plan[i]
                if stage == nil {
                        stage = coreModels.PipelineStage{}
                }
-               // construct task options for Jira
-               options := make(map[string]interface{})
-               options["scopeId"] = bpScope.ScopeId
-               options["connectionId"] = connectionId
 
-               // get scope config from db
-               _, scopeConfig, err := 
scopeHelper.DbHelper().GetScopeAndConfig(connectionId, bpScope.ScopeId)
+               scope, scopeConfig := scopeDetail.Scope, scopeDetail.ScopeConfig
+               // construct task options for Jira
+               task, err := helper.MakePipelinePlanTask(
+                       "jira",
+                       subtaskMetas,
+                       scopeConfig.Entities,
+                       JiraTaskOptions{
+                               ConnectionId: scope.ConnectionId,
+                               BoardId:      scope.BoardId,
+                       },
+               )
                if err != nil {
                        return nil, err
                }
 
-               subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, 
scopeConfig.Entities)
-               if err != nil {
-                       return nil, err
-               }
-               stage = append(stage, &coreModels.PipelineTask{
-                       Plugin:   "jira",
-                       Subtasks: subtasks,
-                       Options:  options,
-               })
+               stage = append(stage, task)
                plan[i] = stage
        }
 
@@ -85,16 +101,12 @@ func makeDataSourcePipelinePlanV200(
 }
 
 func makeScopesV200(
-       bpScopes []*coreModels.BlueprintScope,
-       connectionId uint64,
+       scopeDetails []*srvhelper.ScopeDetail[models.JiraBoard, 
models.JiraScopeConfig],
+       connection *models.JiraConnection,
 ) ([]plugin.Scope, errors.Error) {
        scopes := make([]plugin.Scope, 0)
-       for _, bpScope := range bpScopes {
-               // get board and scope config from db
-               jiraBoard, scopeConfig, err := 
scopeHelper.DbHelper().GetScopeAndConfig(connectionId, bpScope.ScopeId)
-               if err != nil {
-                       return nil, err
-               }
+       for _, scopeDetail := range scopeDetails {
+               jiraBoard, scopeConfig := scopeDetail.Scope, 
scopeDetail.ScopeConfig
                // add board to scopes
                if utils.StringsContains(scopeConfig.Entities, 
plugin.DOMAIN_TYPE_TICKET) {
                        domainBoard := &ticket.Board{
diff --git a/backend/plugins/jira/api/blueprint_v200_test.go 
b/backend/plugins/jira/api/blueprint_v200_test.go
deleted file mode 100644
index c03b932b5..000000000
--- a/backend/plugins/jira/api/blueprint_v200_test.go
+++ /dev/null
@@ -1,110 +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 (
-       "testing"
-
-       coreModels "github.com/apache/incubator-devlake/core/models"
-       "github.com/apache/incubator-devlake/core/models/common"
-       "github.com/apache/incubator-devlake/core/models/domainlayer"
-       "github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
-       "github.com/apache/incubator-devlake/core/plugin"
-       "github.com/apache/incubator-devlake/helpers/unithelper"
-       mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
-       mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
-       "github.com/apache/incubator-devlake/plugins/jira/models"
-       "github.com/stretchr/testify/assert"
-       "github.com/stretchr/testify/mock"
-)
-
-func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
-       mockMeta := mockplugin.NewPluginMeta(t)
-       
mockMeta.On("RootPkgPath").Return("github.com/apache/incubator-devlake/plugins/jira")
-       mockMeta.On("Name").Return("jira").Maybe()
-       err := plugin.RegisterPlugin("jira", mockMeta)
-       assert.Nil(t, err)
-       bs := &coreModels.BlueprintScope{
-               ScopeId: "10",
-       }
-
-       bpScopes := make([]*coreModels.BlueprintScope, 0)
-       bpScopes = append(bpScopes, bs)
-       plan := make(coreModels.PipelinePlan, len(bpScopes))
-       mockBasicRes(t)
-
-       plan, err = makeDataSourcePipelinePlanV200(nil, plan, bpScopes, 
uint64(1))
-       assert.Nil(t, err)
-       scopes, err := makeScopesV200(bpScopes, uint64(1))
-       assert.Nil(t, err)
-
-       expectPlan := coreModels.PipelinePlan{
-               coreModels.PipelineStage{
-                       {
-                               Plugin:   "jira",
-                               Subtasks: []string{},
-                               Options: map[string]interface{}{
-                                       "connectionId": uint64(1),
-                                       "scopeId":      "10",
-                               },
-                       },
-               },
-       }
-       assert.Equal(t, expectPlan, plan)
-
-       expectScopes := make([]plugin.Scope, 0)
-       jiraBoard := &ticket.Board{
-               DomainEntity: domainlayer.DomainEntity{
-                       Id: "jira:JiraBoard:1:10",
-               },
-               Name: "a",
-       }
-
-       expectScopes = append(expectScopes, jiraBoard)
-       assert.Equal(t, expectScopes, scopes)
-}
-
-func mockBasicRes(t *testing.T) {
-       jiraBoard := &models.JiraBoard{
-               Scope: common.Scope{
-                       ConnectionId: 1,
-               },
-               BoardId: 10,
-               Name:    "a",
-       }
-       scopeConfig := &models.JiraScopeConfig{
-               ScopeConfig: common.ScopeConfig{
-                       Entities: []string{plugin.DOMAIN_TYPE_TICKET},
-               },
-       }
-
-       // Refresh Global Variables and set the sql mock
-       mockRes := unithelper.DummyBasicRes(func(mockDal *mockdal.Dal) {
-               mockDal.On("First", 
mock.AnythingOfType("*models.JiraScopeConfig"), mock.Anything).Run(func(args 
mock.Arguments) {
-                       dst := args.Get(0).(*models.JiraScopeConfig)
-                       *dst = *scopeConfig
-               }).Return(nil)
-               mockDal.On("First", mock.AnythingOfType("*models.JiraBoard"), 
mock.Anything).Run(func(args mock.Arguments) {
-                       dst := args.Get(0).(*models.JiraBoard)
-                       *dst = *jiraBoard
-               }).Return(nil)
-       })
-       p := mockplugin.NewPluginMeta(t)
-       p.On("Name").Return("dummy").Maybe()
-       Init(mockRes, p)
-}
diff --git a/backend/plugins/jira/api/connection.go 
b/backend/plugins/jira/api/connection_api.go
similarity index 86%
rename from backend/plugins/jira/api/connection.go
rename to backend/plugins/jira/api/connection_api.go
index f72a182d2..8297ac5d8 100644
--- a/backend/plugins/jira/api/connection.go
+++ b/backend/plugins/jira/api/connection_api.go
@@ -151,10 +151,9 @@ func TestConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/jira/{connectionId}/test [POST]
 func TestExistingConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       connection := &models.JiraConnection{}
-       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
        }
        // test connection
        result, err := testConnection(context.TODO(), connection.JiraConn)
@@ -173,13 +172,7 @@ func TestExistingConnection(input 
*plugin.ApiResourceInput) (*plugin.ApiResource
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/jira/connections [POST]
 func PostConnections(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       // update from request and save to database
-       connection := &models.JiraConnection{}
-       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 jira connection
@@ -191,12 +184,7 @@ func PostConnections(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/jira/connections/{connectionId} [PATCH]
 func PatchConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       connection := &models.JiraConnection{}
-       err := connectionHelper.Patch(connection, input)
-       if err != nil {
-               return nil, err
-       }
-       return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, nil
+       return dsHelper.ConnApi.Patch(input)
 }
 
 // @Summary delete a jira connection
@@ -208,14 +196,7 @@ func PatchConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/jira/connections/{connectionId} [DELETE]
 func DeleteConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       conn := &models.JiraConnection{}
-       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 jira connections
@@ -226,15 +207,7 @@ func DeleteConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/jira/connections [GET]
 func ListConnections(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       var connections []models.JiraConnection
-       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 jira connection detail
@@ -245,10 +218,5 @@ func ListConnections(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {string} errcode.Error "Internal Error"
 // @Router /plugins/jira/connections/{connectionId} [GET]
 func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connection := &models.JiraConnection{}
-       err := connectionHelper.First(connection, input.Params)
-       if err != nil {
-               return nil, err
-       }
-       return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, err
+       return dsHelper.ConnApi.GetDetail(input)
 }
diff --git a/backend/plugins/jira/api/init.go b/backend/plugins/jira/api/init.go
index 663742edf..4827fa795 100644
--- a/backend/plugins/jira/api/init.go
+++ b/backend/plugins/jira/api/init.go
@@ -22,50 +22,35 @@ import (
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/jira/models"
-       "github.com/apache/incubator-devlake/plugins/jira/tasks/apiv2models"
        "github.com/go-playground/validator/v10"
 )
 
 var vld *validator.Validate
-var connectionHelper *api.ConnectionApiHelper
-var scopeHelper *api.ScopeApiHelper[models.JiraConnection, models.JiraBoard, 
models.JiraScopeConfig]
-var remoteHelper *api.RemoteApiHelper[models.JiraConnection, models.JiraBoard, 
apiv2models.Board, api.NoRemoteGroupResponse]
+
 var basicRes context.BasicRes
-var scHelper *api.ScopeConfigHelper[models.JiraScopeConfig, 
*models.JiraScopeConfig]
+var dsHelper *api.DsHelper[models.JiraConnection, models.JiraBoard, 
models.JiraScopeConfig]
+var raProxy *api.DsRemoteApiProxyHelper[models.JiraConnection]
+var raScopeList *api.DsRemoteApiScopeListHelper[models.JiraConnection, 
models.JiraBoard, JiraRemotePagination]
+var raScopeSearch *api.DsRemoteApiScopeSearchHelper[models.JiraConnection, 
models.JiraBoard]
 
 func Init(br context.BasicRes, p plugin.PluginMeta) {
-
        basicRes = br
        vld = validator.New()
-       connectionHelper = api.NewConnectionHelper(
-               basicRes,
-               vld,
+       dsHelper = api.NewDataSourceHelper[
+               models.JiraConnection,
+               models.JiraBoard,
+               models.JiraScopeConfig,
+       ](
+               br,
                p.Name(),
-       )
-       params := &api.ReflectionParameters{
-               ScopeIdFieldName:     "BoardId",
-               ScopeIdColumnName:    "board_id",
-               RawScopeParamName:    "BoardId",
-               SearchScopeParamName: "name",
-       }
-       scopeHelper = api.NewScopeHelper[models.JiraConnection, 
models.JiraBoard, models.JiraScopeConfig](
-               basicRes,
-               vld,
-               connectionHelper,
-               api.NewScopeDatabaseHelperImpl[models.JiraConnection, 
models.JiraBoard, models.JiraScopeConfig](
-                       basicRes, connectionHelper, params),
-               params,
+               []string{"full_name"},
+               func(c models.JiraConnection) models.JiraConnection {
+                       return c.Sanitize()
+               },
+               nil,
                nil,
        )
-
-       remoteHelper = api.NewRemoteHelper[models.JiraConnection, 
models.JiraBoard, apiv2models.Board, api.NoRemoteGroupResponse](
-               basicRes,
-               vld,
-               connectionHelper,
-       )
-       scHelper = api.NewScopeConfigHelper[models.JiraScopeConfig, 
*models.JiraScopeConfig](
-               basicRes,
-               vld,
-               p.Name(),
-       )
+       raProxy = 
api.NewDsRemoteApiProxyHelper[models.JiraConnection](dsHelper.ConnApi.ModelApiHelper)
+       raScopeList = api.NewDsRemoteApiScopeListHelper[models.JiraConnection, 
models.JiraBoard, JiraRemotePagination](raProxy, listJiraRemoteScopes)
+       raScopeSearch = 
api.NewDsRemoteApiScopeSearchHelper[models.JiraConnection, 
models.JiraBoard](raProxy, searchJiraRemoteBoards)
 }
diff --git a/backend/plugins/jira/api/proxy.go 
b/backend/plugins/jira/api/proxy.go
deleted file mode 100644
index 8454b300b..000000000
--- a/backend/plugins/jira/api/proxy.go
+++ /dev/null
@@ -1,51 +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 (
-       "context"
-       "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"
-)
-
-func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       connection := &models.JiraConnection{}
-       err := connectionHelper.First(connection, input.Params)
-       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
-       }
-       return &plugin.ApiResourceOutput{Status: resp.StatusCode, ContentType: 
resp.Header.Get("Content-Type"), Body: body}, nil
-}
diff --git a/backend/plugins/jira/api/remote.go 
b/backend/plugins/jira/api/remote.go
deleted file mode 100644
index 512bfbb22..000000000
--- a/backend/plugins/jira/api/remote.go
+++ /dev/null
@@ -1,136 +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"
-       "net/url"
-
-       "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/jira/models"
-       "github.com/apache/incubator-devlake/plugins/jira/tasks/apiv2models"
-)
-
-// RemoteScopes list all available scope for users
-// @Summary list all available scope for users
-// @Description list all available scope for users
-// @Tags plugins/jira
-// @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/jira/connections/{connectionId}/remote-scopes [GET]
-func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return remoteHelper.GetScopesFromRemote(input,
-               nil,
-               func(basicRes context.BasicRes, gid string, queryData 
*api.RemoteQueryData, connection models.JiraConnection) ([]apiv2models.Board, 
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("agile/1.0/board", query, nil)
-                       if err != nil {
-                               return nil, err
-                       }
-
-                       resBody := struct {
-                               MaxResults int                 
`json:"maxResults"`
-                               StartAt    int                 `json:"startAt"`
-                               Values     []apiv2models.Board `json:"values"`
-                       }{}
-
-                       err = api.UnmarshalResponse(res, &resBody)
-                       if err != nil {
-                               return nil, err
-                       }
-
-                       if (queryData.PerPage != resBody.MaxResults) ||
-                               (((queryData.Page - 1) * queryData.PerPage) != 
resBody.StartAt) {
-                               analyzingQuery(resBody.MaxResults, 
resBody.StartAt, queryData)
-                       }
-
-                       return resBody.Values, err
-               })
-}
-
-// SearchRemoteScopes use the Search API and only return board
-// @Summary use the Search API and only return board
-// @Description use the Search API and only return board
-// @Tags plugins/jira
-// @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/jira/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.JiraConnection) ([]apiv2models.Board, 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("name", queryData.Search[0])
-
-                       // request search
-                       res, err := apiClient.Get("agile/1.0/board?", query, 
nil)
-                       if err != nil {
-                               return nil, err
-                       }
-                       var resBody struct {
-                               Values []apiv2models.Board `json:"values"`
-                       }
-                       err = api.UnmarshalResponse(res, &resBody)
-                       if err != nil {
-                               return nil, err
-                       }
-
-                       return resBody.Values, err
-
-               })
-}
-
-func initialQuery(queryData *api.RemoteQueryData) url.Values {
-       query := url.Values{}
-       query.Set("maxResults", fmt.Sprintf("%v", queryData.PerPage))
-       query.Set("startAt", fmt.Sprintf("%v", 
(queryData.Page-1)*queryData.PerPage))
-       return query
-}
-
-func analyzingQuery(maxResults int, startAt int, queryData 
*api.RemoteQueryData) {
-       if maxResults != 0 {
-               queryData.PerPage = maxResults
-               queryData.Page = startAt/maxResults + 1
-       }
-}
diff --git a/backend/plugins/jira/api/remote_api.go 
b/backend/plugins/jira/api/remote_api.go
new file mode 100644
index 000000000..66767053a
--- /dev/null
+++ b/backend/plugins/jira/api/remote_api.go
@@ -0,0 +1,154 @@
+/*
+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/jira/models"
+       "github.com/apache/incubator-devlake/plugins/jira/tasks/apiv2models"
+)
+
+type JiraRemotePagination struct {
+       MaxResults int `json:"maxResults"`
+       StartAt    int `json:"startAt"`
+}
+
+func queryJiraAgileBoards(
+       apiClient plugin.ApiClient,
+       keyword string,
+       page JiraRemotePagination,
+) (
+       children []dsmodels.DsRemoteApiScopeListEntry[models.JiraBoard],
+       nextPage *JiraRemotePagination,
+       err errors.Error,
+) {
+       if page.MaxResults == 0 {
+               page.MaxResults = 100
+       }
+       res, err := apiClient.Get("agile/1.0/board", url.Values{
+               "maxResults": {fmt.Sprintf("%v", page.MaxResults)},
+               "startAt":    {fmt.Sprintf("%v", page.StartAt)},
+               "name":       {keyword},
+       }, nil)
+       if err != nil {
+               return
+       }
+
+       resBody := struct {
+               JiraRemotePagination
+               IsLast bool                `json:"isLast"`
+               Values []apiv2models.Board `json:"values"`
+       }{}
+
+       err = api.UnmarshalResponse(res, &resBody)
+       if err != nil {
+               return
+       }
+
+       for _, board := range resBody.Values {
+               children = append(children, 
dsmodels.DsRemoteApiScopeListEntry[models.JiraBoard]{
+                       Type:     api.RAS_ENTRY_TYPE_SCOPE,
+                       Id:       fmt.Sprintf("%v", board.ID),
+                       ParentId: nil,
+                       Name:     board.Name,
+                       FullName: board.Name,
+                       Data:     board.ToToolLayer(0),
+               })
+       }
+
+       return
+}
+
+func listJiraRemoteScopes(
+       _ *models.JiraConnection,
+       apiClient plugin.ApiClient,
+       groupId string,
+       page JiraRemotePagination,
+) (
+       children []dsmodels.DsRemoteApiScopeListEntry[models.JiraBoard],
+       nextPage *JiraRemotePagination,
+       err errors.Error,
+) {
+       return queryJiraAgileBoards(apiClient, "", page)
+}
+
+// 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.JiraBoard]
+// @Tags plugins/jira
+// @Router /plugins/jira/connections/{connectionId}/remote-scopes [GET]
+func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return raScopeList.Get(input)
+}
+
+func searchJiraRemoteBoards(
+       apiClient plugin.ApiClient,
+       params *dsmodels.DsRemoteApiScopeSearchParams,
+) (
+       children []dsmodels.DsRemoteApiScopeListEntry[models.JiraBoard],
+       err errors.Error,
+) {
+       if params.Page == 0 {
+               params.Page = 1
+       }
+       page := JiraRemotePagination{
+               MaxResults: params.PageSize,
+               StartAt:    (params.Page - 1) * params.PageSize,
+       }
+       children, _, err = queryJiraAgileBoards(apiClient, params.Search, page)
+       return
+}
+
+// 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.JiraBoard] "the 
parentIds are always null"
+// @Tags plugins/jira
+// @Router /plugins/jira/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"
+// @Router /plugins/jira/connections/{connectionId}/proxy/{path} [GET]
+func Proxy(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return raProxy.Proxy(input)
+}
diff --git a/backend/plugins/jira/api/scope.go 
b/backend/plugins/jira/api/scope_api.go
similarity index 91%
rename from backend/plugins/jira/api/scope.go
rename to backend/plugins/jira/api/scope_api.go
index 9f2344ab3..cfb08d29c 100644
--- a/backend/plugins/jira/api/scope.go
+++ b/backend/plugins/jira/api/scope_api.go
@@ -31,12 +31,8 @@ import (
        "github.com/apache/incubator-devlake/plugins/jira/tasks/apiv2models"
 )
 
-type ScopeRes struct {
-       models.JiraBoard
-       api.ScopeResDoc[models.JiraScopeConfig]
-}
-
-type ScopeReq api.ScopeReq[models.JiraBoard]
+type PutScopesReqBody api.PutScopesReqBody[models.JiraBoard]
+type ScopeDetail api.ScopeDetail[models.JiraBoard, models.JiraScopeConfig]
 
 // PutScope create or update jira board
 // @Summary create or update jira board
@@ -45,13 +41,13 @@ type ScopeReq api.ScopeReq[models.JiraBoard]
 // @Accept application/json
 // @Param connectionId path int false "connection ID"
 // @Param searchTerm query string false "search term for scope name"
-// @Param scope body ScopeReq true "json"
+// @Param scope body PutScopesReqBody true "json"
 // @Success 200  {object} []models.JiraBoard
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scopes [PUT]
 func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Put(input)
+       return dsHelper.ScopeApi.PutMultiple(input)
 }
 
 // UpdateScope patch to jira board
@@ -67,7 +63,7 @@ func PutScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scopes/{scopeId} [PATCH]
 func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Update(input)
+       return dsHelper.ScopeApi.Patch(input)
 }
 
 // GetScopeList get Jira boards
@@ -78,12 +74,12 @@ func UpdateScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, err
 // @Param pageSize query int false "page size, default 50"
 // @Param page query int false "page size, default 1"
 // @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
-// @Success 200  {object} []ScopeRes
+// @Success 200  {object} []ScopeDetail
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/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 Jira board
@@ -92,12 +88,12 @@ func GetScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
 // @Tags plugins/jira
 // @Param connectionId path int false "connection ID"
 // @Param scopeId path int false "board ID"
-// @Success 200  {object} ScopeRes
+// @Success 200  {object} ScopeDetail
 // @Failure 400  {object} shared.ApiBody "Bad Request"
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/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
@@ -113,7 +109,7 @@ func GetScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scopes/{scopeId} [DELETE]
 func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
-       return scopeHelper.Delete(input)
+       return dsHelper.ScopeApi.Delete(input)
 }
 
 func GetApiJira(op *tasks.JiraOptions, apiClient plugin.ApiClient) 
(*apiv2models.Board, errors.Error) {
diff --git a/backend/plugins/jira/api/scope_config.go 
b/backend/plugins/jira/api/scope_config_api.go
similarity index 96%
rename from backend/plugins/jira/api/scope_config.go
rename to backend/plugins/jira/api/scope_config_api.go
index 6d978bee0..58eb46ae4 100644
--- a/backend/plugins/jira/api/scope_config.go
+++ b/backend/plugins/jira/api/scope_config_api.go
@@ -30,7 +30,6 @@ import (
        "github.com/apache/incubator-devlake/core/models/common"
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
-       "github.com/apache/incubator-devlake/plugins/jira/models"
 )
 
 type genRegexReq struct {
@@ -64,7 +63,7 @@ type repo struct {
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scope-configs [POST]
 func CreateScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return scHelper.Create(input)
+       return dsHelper.ScopeConfigApi.Post(input)
 }
 
 // UpdateScopeConfig update scope config for Jira
@@ -80,7 +79,7 @@ func CreateScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutpu
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scope-configs/{id} [PATCH]
 func UpdateScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return scHelper.Update(input)
+       return dsHelper.ScopeConfigApi.Patch(input)
 }
 
 // GetScopeConfig return one scope config
@@ -94,7 +93,7 @@ func UpdateScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutpu
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scope-configs/{id} [GET]
 func GetScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return scHelper.Get(input)
+       return dsHelper.ScopeConfigApi.GetDetail(input)
 }
 
 // GetScopeConfigList return all scope configs
@@ -109,7 +108,7 @@ func GetScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scope-configs [GET]
 func GetScopeConfigList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return scHelper.List(input)
+       return dsHelper.ScopeConfigApi.GetAll(input)
 }
 
 // DeleteScopeConfig delete a scope config
@@ -123,7 +122,7 @@ func GetScopeConfigList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutp
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/scope-configs/{id} [DELETE]
 func DeleteScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       return scHelper.Delete(input)
+       return dsHelper.ScopeConfigApi.Delete(input)
 }
 
 // GetApplicationTypes return issue application types
@@ -137,8 +136,7 @@ func DeleteScopeConfig(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutpu
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/application-types [GET]
 func GetApplicationTypes(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       var connection models.JiraConnection
-       err := connectionHelper.First(&connection, input.Params)
+       connection, err := dsHelper.ConnApi.FindByPk(input)
        if err != nil {
                return nil, err
        }
@@ -147,7 +145,7 @@ func GetApplicationTypes(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOut
                return nil, errors.BadInput.New("key is empty")
        }
 
-       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, &connection)
+       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, connection)
        if err != nil {
                return nil, err
        }
@@ -202,8 +200,7 @@ func GetApplicationTypes(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOut
 // @Failure 500  {object} shared.ApiBody "Internal Error"
 // @Router /plugins/jira/connections/{connectionId}/dev-panel-commits [GET]
 func GetCommitsURLs(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
-       var connection models.JiraConnection
-       err := connectionHelper.First(&connection, input.Params)
+       connection, err := dsHelper.ConnApi.FindByPk(input)
        if err != nil {
                return nil, err
        }
@@ -218,7 +215,7 @@ func GetCommitsURLs(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
                return nil, errors.BadInput.New("applicationType is empty")
        }
        // get issue ID from issue key
-       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, &connection)
+       apiClient, err := api.NewApiClientFromConnection(context.TODO(), 
basicRes, connection)
        if err != nil {
                return nil, err
        }
diff --git a/backend/plugins/jira/impl/impl.go 
b/backend/plugins/jira/impl/impl.go
index 0b428bbce..b156eac31 100644
--- a/backend/plugins/jira/impl/impl.go
+++ b/backend/plugins/jira/impl/impl.go
@@ -210,19 +210,6 @@ func (p Jira) PrepareTaskData(taskCtx plugin.TaskContext, 
options map[string]int
                        return nil, errors.Default.Wrap(err, fmt.Sprintf("fail 
to find board: %d", op.BoardId))
                }
        }
-
-       if op.BoardId == 0 && op.ScopeId != "" {
-               var jiraBoard models.JiraBoard
-               // get repo from db
-               err = db.First(&jiraBoard, dal.Where(`connection_id = ? and 
board_id = ?`, connection.ID, op.ScopeId))
-               if err != nil {
-                       return nil, errors.Default.Wrap(err, fmt.Sprintf("fail 
to find board%s", op.ScopeId))
-               }
-               op.BoardId = jiraBoard.BoardId
-               if op.ScopeConfigId == 0 {
-                       op.ScopeConfigId = jiraBoard.ScopeConfigId
-               }
-       }
        if op.ScopeConfig == nil && op.ScopeConfigId != 0 {
                var scopeConfig models.JiraScopeConfig
                err = taskCtx.GetDal().First(&scopeConfig, dal.Where("id = ?", 
op.ScopeConfigId))
@@ -312,7 +299,7 @@ func (p Jira) ApiResources() 
map[string]map[string]plugin.ApiResourceHandler {
                        "POST": api.CreateScopeConfig,
                        "GET":  api.GetScopeConfigList,
                },
-               "connections/:connectionId/scope-configs/:id": {
+               "connections/:connectionId/scope-configs/:scopeConfigId": {
                        "PATCH":  api.UpdateScopeConfig,
                        "GET":    api.GetScopeConfig,
                        "DELETE": api.DeleteScopeConfig,
diff --git a/backend/plugins/jira/tasks/task_data.go 
b/backend/plugins/jira/tasks/task_data.go
index 3a9b01d26..42433bf9c 100644
--- a/backend/plugins/jira/tasks/task_data.go
+++ b/backend/plugins/jira/tasks/task_data.go
@@ -29,7 +29,6 @@ type JiraOptions struct {
        ConnectionId  uint64                  `json:"connectionId"`
        BoardId       uint64                  `json:"boardId"`
        ScopeConfig   *models.JiraScopeConfig `json:"scopeConfig"`
-       ScopeId       string
        ScopeConfigId uint64
        PageSize      int
 }


Reply via email to