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 }
