This is an automated email from the ASF dual-hosted git repository. likyh pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push: new 807a5ce99 Issues/4567 zentao bp (#4705) 807a5ce99 is described below commit 807a5ce99734c13ced99c932cfb36b1dc8aeef2a Author: Likyh <yang...@meri.co> AuthorDate: Mon Mar 20 11:09:23 2023 +0800 Issues/4567 zentao bp (#4705) * fix: update scope helper * feat: add bp support for zentao * feat: support zentao bp in config-ui * feat: update zentao option * fix: fix task bug * fix: update e2e test * fix: for linter * fix: for review --- .../helpers/pluginhelper/api/remote_api_helper.go | 30 +++- backend/helpers/pluginhelper/api/scope_helper.go | 14 +- backend/plugins/bamboo/api/init.go | 4 +- backend/plugins/bamboo/models/project.go | 14 +- backend/plugins/zentao/api/blueprint.go | 67 -------- backend/plugins/zentao/api/blueprint_V200_test.go | 175 +++++++++++++++++++++ backend/plugins/zentao/api/blueprint_v200.go | 140 +++++++++++++++++ backend/plugins/zentao/api/init.go | 32 ++++ backend/plugins/zentao/api/remote.go | 138 ++++++++++++++++ backend/plugins/zentao/api/scope.go | 131 +++++++++++++++ backend/plugins/zentao/e2e/account_test.go | 1 - backend/plugins/zentao/e2e/bug_test.go | 1 - backend/plugins/zentao/e2e/department_test.go | 1 - backend/plugins/zentao/e2e/execution_test.go | 12 +- backend/plugins/zentao/e2e/product_test.go | 12 +- .../e2e/raw_tables/_raw_zentao_api_accounts.csv | 24 +-- .../zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv | 12 +- .../e2e/raw_tables/_raw_zentao_api_departments.csv | 24 +-- .../e2e/raw_tables/_raw_zentao_api_executions.csv | 6 +- .../raw_tables/_raw_zentao_api_executions_real.csv | 2 - .../e2e/raw_tables/_raw_zentao_api_products.csv | 4 +- .../e2e/raw_tables/_raw_zentao_api_stories.csv | 18 +-- .../e2e/raw_tables/_raw_zentao_api_tasks.csv | 10 +- .../snapshot_tables/_tool_zentao_executions.csv | 3 +- .../e2e/snapshot_tables/_tool_zentao_tasks.csv | 6 +- .../e2e/snapshot_tables/boards_execution.csv | 2 - .../e2e/snapshot_tables/execution_board_sprint.csv | 3 + .../e2e/snapshot_tables/execution_sprint.csv | 3 + backend/plugins/zentao/e2e/story_test.go | 1 - backend/plugins/zentao/e2e/task_test.go | 1 - backend/plugins/zentao/impl/impl.go | 27 +++- backend/plugins/zentao/models/product.go | 116 ++++++++++---- backend/plugins/zentao/models/project.go | 84 +++++----- backend/plugins/zentao/tasks/account_collector.go | 2 +- backend/plugins/zentao/tasks/account_convertor.go | 1 - backend/plugins/zentao/tasks/account_extractor.go | 1 - backend/plugins/zentao/tasks/bug_collector.go | 5 +- backend/plugins/zentao/tasks/bug_convertor.go | 1 - backend/plugins/zentao/tasks/bug_extractor.go | 1 - .../plugins/zentao/tasks/department_collector.go | 2 +- .../plugins/zentao/tasks/department_convertor.go | 1 - .../plugins/zentao/tasks/department_extractor.go | 1 - .../plugins/zentao/tasks/execution_collector.go | 17 +- .../plugins/zentao/tasks/execution_convertor.go | 42 +++-- .../plugins/zentao/tasks/execution_extractor.go | 2 +- backend/plugins/zentao/tasks/product_collector.go | 76 --------- backend/plugins/zentao/tasks/product_convertor.go | 6 +- backend/plugins/zentao/tasks/product_extractor.go | 101 ------------ backend/plugins/zentao/tasks/project_collector.go | 78 --------- .../{product_convertor.go => project_convertor.go} | 38 ++--- backend/plugins/zentao/tasks/project_extractor.go | 69 -------- backend/plugins/zentao/tasks/story_collector.go | 5 +- backend/plugins/zentao/tasks/story_convertor.go | 3 +- backend/plugins/zentao/tasks/story_extractor.go | 1 - backend/plugins/zentao/tasks/task_collector.go | 29 +++- backend/plugins/zentao/tasks/task_convertor.go | 6 +- backend/plugins/zentao/tasks/task_data.go | 24 ++- backend/plugins/zentao/tasks/task_extractor.go | 1 - backend/plugins/zentao/zentao.go | 2 - .../blueprint/create/step-three/use-columns.tsx | 2 +- .../pages/blueprint/detail/panel/configuration.tsx | 2 +- .../src/pages/pipeline/components/task/index.tsx | 7 + config-ui/src/plugins/components/data-scope/api.ts | 6 + .../src/plugins/components/data-scope/index.tsx | 5 + .../components/data-scope/use-data-scope.ts | 31 +++- config-ui/src/plugins/register/zentao/config.ts | 1 - .../src/plugins/register/zentao/data-scope.tsx | 54 +++++++ config-ui/src/plugins/register/zentao/index.ts | 1 + .../plugins/register/zentao/{index.ts => types.ts} | 8 +- 69 files changed, 1103 insertions(+), 647 deletions(-) diff --git a/backend/helpers/pluginhelper/api/remote_api_helper.go b/backend/helpers/pluginhelper/api/remote_api_helper.go index 93be9e04a..64840fe4b 100644 --- a/backend/helpers/pluginhelper/api/remote_api_helper.go +++ b/backend/helpers/pluginhelper/api/remote_api_helper.go @@ -74,6 +74,30 @@ func NewRemoteHelper[Conn plugin.ApiConnection, Scope plugin.ToolLayerScope, Api } } +type NoRemoteGroupResponse struct { +} + +func (NoRemoteGroupResponse) GroupId() string { + return "" +} + +func (NoRemoteGroupResponse) GroupName() string { + return "" +} + +type BaseRemoteGroupResponse struct { + Id string + Name string +} + +func (g BaseRemoteGroupResponse) GroupId() string { + return g.Id +} + +func (g BaseRemoteGroupResponse) GroupName() string { + return g.Name +} + const remoteScopesPerPage int = 100 const TypeProject string = "scope" const TypeGroup string = "group" @@ -115,7 +139,9 @@ func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) GetScopesFromRemote(inpu // list groups part if queryData.Tag == TypeGroup { var resBody []Group - resBody, err = getGroup(r.basicRes, gid, queryData, connection) + if getGroup != nil { + resBody, err = getGroup(r.basicRes, gid, queryData, connection) + } if err != nil { return nil, err } @@ -144,7 +170,7 @@ func (r *RemoteApiHelper[Conn, Scope, ApiScope, Group]) GetScopesFromRemote(inpu } // list projects part - if queryData.Tag == TypeProject { + if queryData.Tag == TypeProject && getScope != nil { var resBody []ApiScope resBody, err = getScope(r.basicRes, gid, queryData, connection) if err != nil { diff --git a/backend/helpers/pluginhelper/api/scope_helper.go b/backend/helpers/pluginhelper/api/scope_helper.go index 749372831..e0eaee3e6 100644 --- a/backend/helpers/pluginhelper/api/scope_helper.go +++ b/backend/helpers/pluginhelper/api/scope_helper.go @@ -83,7 +83,7 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) Put(input *plugin.ApiResourceInput) (* } err := errors.Convert(DecodeMapStruct(input.Body, &req)) if err != nil { - return nil, errors.BadInput.Wrap(err, "decoding Github repo error") + return nil, errors.BadInput.Wrap(err, "decoding scope error") } // Extract the connection ID from the input.Params map connectionId, _ := extractFromReqParam(input.Params) @@ -117,9 +117,11 @@ func (c *ScopeApiHelper[Conn, Scope, Tr]) Put(input *plugin.ApiResourceInput) (* return nil, err } } - err = c.save(&req.Data) - if err != nil { - return nil, err + if req.Data != nil && len(req.Data) > 0 { + err = c.save(&req.Data) + if err != nil { + return nil, err + } } // Save the scopes to the database @@ -284,13 +286,13 @@ func setScopeFields(p interface{}, connectionId uint64, createdDate *time.Time, // set CreatedDate createdDateField := pValue.FieldByName("CreatedDate") - if createdDateField.IsValid() { + if createdDateField.IsValid() && createdDateField.Type().AssignableTo(reflect.TypeOf(createdDate)) { createdDateField.Set(reflect.ValueOf(createdDate)) } // set UpdatedDate updatedDateField := pValue.FieldByName("UpdatedDate") - if !updatedDateField.IsValid() { + if !updatedDateField.IsValid() || (updatedDate != nil && !updatedDateField.Type().AssignableTo(reflect.TypeOf(updatedDate))) { return } if updatedDate == nil { diff --git a/backend/plugins/bamboo/api/init.go b/backend/plugins/bamboo/api/init.go index 7705fd797..e70961d82 100644 --- a/backend/plugins/bamboo/api/init.go +++ b/backend/plugins/bamboo/api/init.go @@ -27,7 +27,7 @@ import ( var vld *validator.Validate var connectionHelper *api.ConnectionApiHelper var scopeHelper *api.ScopeApiHelper[models.BambooConnection, models.BambooProject, models.BambooTransformationRule] -var remoteHelper *api.RemoteApiHelper[models.BambooConnection, models.BambooProject, models.ApiBambooProject, models.GroupResponse] +var remoteHelper *api.RemoteApiHelper[models.BambooConnection, models.BambooProject, models.ApiBambooProject, api.NoRemoteGroupResponse] var basicRes context.BasicRes @@ -43,7 +43,7 @@ func Init(br context.BasicRes) { vld, connectionHelper, ) - remoteHelper = api.NewRemoteHelper[models.BambooConnection, models.BambooProject, models.ApiBambooProject, models.GroupResponse]( + remoteHelper = api.NewRemoteHelper[models.BambooConnection, models.BambooProject, models.ApiBambooProject, api.NoRemoteGroupResponse]( basicRes, vld, connectionHelper, diff --git a/backend/plugins/bamboo/models/project.go b/backend/plugins/bamboo/models/project.go index 2232604e6..56fe9879d 100644 --- a/backend/plugins/bamboo/models/project.go +++ b/backend/plugins/bamboo/models/project.go @@ -21,10 +21,11 @@ import ( "encoding/json" "github.com/apache/incubator-devlake/core/models/common" "github.com/apache/incubator-devlake/core/plugin" + "github.com/apache/incubator-devlake/helpers/pluginhelper/api" ) var _ plugin.ToolLayerScope = (*BambooProject)(nil) -var _ plugin.ApiGroup = (*GroupResponse)(nil) +var _ plugin.ApiGroup = (*api.NoRemoteGroupResponse)(nil) var _ plugin.ApiScope = (*ApiBambooProject)(nil) type BambooProject struct { @@ -103,14 +104,3 @@ func (apiProject ApiBambooProject) ConvertApiScope() plugin.ToolLayerScope { b.Href = apiProject.Link.Href return b } - -type GroupResponse struct { -} - -func (p GroupResponse) GroupId() string { - return "" -} - -func (p GroupResponse) GroupName() string { - return "" -} diff --git a/backend/plugins/zentao/api/blueprint.go b/backend/plugins/zentao/api/blueprint.go deleted file mode 100644 index fc0625597..000000000 --- a/backend/plugins/zentao/api/blueprint.go +++ /dev/null @@ -1,67 +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 ( - "encoding/json" - "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/zentao/tasks" -) - -func MakePipelinePlan(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) { - var err error - plan := make(plugin.PipelinePlan, len(scope)) - for i, scopeElem := range scope { - taskOptions := make(map[string]interface{}) - err = json.Unmarshal(scopeElem.Options, &taskOptions) - if err != nil { - return nil, errors.Default.WrapRaw(err) - } - taskOptions["connectionId"] = connectionId - - /* - var transformationRules tasks.JiraTransformationRule - if len(scopeElem.Transformation) > 0 { - err = json.Unmarshal(scopeElem.Transformation, &transformationRules) - if err != nil { - return nil, err - } - } - */ - //taskOptions["transformationRules"] = transformationRules - _, err := tasks.DecodeAndValidateTaskOptions(taskOptions) - if err != nil { - return nil, errors.Default.WrapRaw(err) - } - // subtasks - subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, scopeElem.Entities) - if err != nil { - return nil, errors.Default.WrapRaw(err) - } - plan[i] = plugin.PipelineStage{ - { - Plugin: "zentao", - Subtasks: subtasks, - Options: taskOptions, - }, - } - } - return plan, nil -} diff --git a/backend/plugins/zentao/api/blueprint_V200_test.go b/backend/plugins/zentao/api/blueprint_V200_test.go new file mode 100644 index 000000000..cb96293e5 --- /dev/null +++ b/backend/plugins/zentao/api/blueprint_V200_test.go @@ -0,0 +1,175 @@ +/* +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" + + "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" + helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api" + mockcontext "github.com/apache/incubator-devlake/mocks/core/context" + mockdal "github.com/apache/incubator-devlake/mocks/core/dal" + mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin" + "github.com/apache/incubator-devlake/plugins/zentao/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestMakeDataSourcePipelinePlanV200(t *testing.T) { + connection := &models.ZentaoConnection{ + BaseConnection: helper.BaseConnection{ + Name: "zentao-test", + Model: common.Model{ + ID: 1, + }, + }, + ZentaoConn: models.ZentaoConn{ + RestConnection: helper.RestConnection{ + Endpoint: "https://zentao.example.org/api.php/v1/", + Proxy: "", + RateLimitPerHour: 0, + }, + BasicAuth: helper.BasicAuth{ + Username: "Username", + Password: "Password", + }, + }, + } + mockMeta := mockplugin.NewPluginMeta(t) + mockMeta.On("RootPkgPath").Return("github.com/apache/incubator-devlake/plugins/zentao") + err := plugin.RegisterPlugin("zentao", mockMeta) + assert.Nil(t, err) + // Refresh Global Variables and set the sql mock + basicRes = NewMockBasicRes() + bs := &plugin.BlueprintScopeV200{ + Entities: []string{"TICKET"}, + Id: "project/1", + } + bs2 := &plugin.BlueprintScopeV200{ + Entities: []string{"TICKET"}, + Id: "product/1", + } + bpScopes := make([]*plugin.BlueprintScopeV200, 0) + bpScopes = append(bpScopes, bs, bs2) + syncPolicy := &plugin.BlueprintSyncPolicy{} + + plan := make(plugin.PipelinePlan, len(bpScopes)) + plan, scopes, err := makePipelinePlanV200(nil, plan, bpScopes, connection, syncPolicy) + assert.Nil(t, err) + basicRes = NewMockBasicRes() + + expectPlan := plugin.PipelinePlan{ + plugin.PipelineStage{ + { + Plugin: "zentao", + Subtasks: []string{}, + Options: map[string]interface{}{ + "ConnectionId": uint64(1), + "productId": int64(0), + "projectId": int64(1), + }, + }, + }, + plugin.PipelineStage{ + { + Plugin: "zentao", + Subtasks: []string{}, + Options: map[string]interface{}{ + "ConnectionId": uint64(1), + "productId": int64(1), + "projectId": int64(0), + }, + }, + }, + } + assert.Equal(t, expectPlan, plan) + expectScopes := make([]plugin.Scope, 0) + scopeTicket1 := &ticket.Board{ + DomainEntity: domainlayer.DomainEntity{ + Id: "zentao:ZentaoProject:1:1", + }, + Name: "test/testRepo", + Description: "", + Url: "", + CreatedDate: nil, + Type: `project`, + } + scopeTicket2 := &ticket.Board{ + DomainEntity: domainlayer.DomainEntity{ + Id: "zentao:ZentaoProduct:1:1", + }, + Name: "test/testRepo", + Description: "", + Url: "", + CreatedDate: nil, + Type: `product/normal`, + } + + expectScopes = append(expectScopes, scopeTicket1, scopeTicket2) + assert.Equal(t, expectScopes, scopes) +} + +// NewMockBasicRes FIXME ... +func NewMockBasicRes() *mockcontext.BasicRes { + testZentaoProduct := &models.ZentaoProduct{ + ConnectionId: 1, + Id: 1, + Name: "test/testRepo", + Type: `product/normal`, + //TransformationRuleId: 1, + } + testZentaoProject := &models.ZentaoProject{ + ConnectionId: 1, + Id: 1, + Name: "test/testRepo", + Type: `project`, + //TransformationRuleId: 1, + } + + //testTransformationRule := &models.ZentaoTransformation{ + // Model: common.Model{ + // ID: 1, + // }, + // Name: "Zentao transformation rule", + //} + mockRes := new(mockcontext.BasicRes) + mockDal := new(mockdal.Dal) + + mockDal.On("First", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + dst := args.Get(0).(*models.ZentaoProject) + *dst = *testZentaoProject + }).Return(nil).Once() + + mockDal.On("First", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + dst := args.Get(0).(*models.ZentaoProduct) + *dst = *testZentaoProduct + }).Return(nil).Once() + + //mockDal.On("First", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + // dst := args.Get(0).(*models.ZentaoTransformation) + // *dst = *testTransformationRule + //}).Return(nil).Once() + + mockRes.On("GetDal").Return(mockDal) + mockRes.On("GetConfig", mock.Anything).Return("") + + return mockRes +} diff --git a/backend/plugins/zentao/api/blueprint_v200.go b/backend/plugins/zentao/api/blueprint_v200.go new file mode 100644 index 000000000..6d0805247 --- /dev/null +++ b/backend/plugins/zentao/api/blueprint_v200.go @@ -0,0 +1,140 @@ +/* +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" + "github.com/apache/incubator-devlake/core/dal" + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/models/domainlayer" + "github.com/apache/incubator-devlake/core/models/domainlayer/didgen" + "github.com/apache/incubator-devlake/core/models/domainlayer/ticket" + "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/plugins/zentao/models" + "github.com/apache/incubator-devlake/plugins/zentao/tasks" + "github.com/go-playground/validator/v10" + "strings" + "time" +) + +func MakeDataSourcePipelinePlanV200(subtaskMetas []plugin.SubTaskMeta, connectionId uint64, bpScopes []*plugin.BlueprintScopeV200, syncPolicy *plugin.BlueprintSyncPolicy) (plugin.PipelinePlan, []plugin.Scope, errors.Error) { + connectionHelper := helper.NewConnectionHelper(basicRes, validator.New()) + // get the connection info for url + connection := &models.ZentaoConnection{} + err := connectionHelper.FirstById(connection, connectionId) + if err != nil { + return nil, nil, err + } + + plan := make(plugin.PipelinePlan, len(bpScopes)) + plan, scopes, err := makePipelinePlanV200(subtaskMetas, plan, bpScopes, connection, syncPolicy) + if err != nil { + return nil, nil, err + } + + return plan, scopes, nil +} + +func makePipelinePlanV200( + subtaskMetas []plugin.SubTaskMeta, + plan plugin.PipelinePlan, + bpScopes []*plugin.BlueprintScopeV200, + connection *models.ZentaoConnection, + syncPolicy *plugin.BlueprintSyncPolicy, +) (plugin.PipelinePlan, []plugin.Scope, errors.Error) { + var err errors.Error + domainScopes := make([]plugin.Scope, 0) + for i, bpScope := range bpScopes { + stage := plan[i] + if stage == nil { + stage = plugin.PipelineStage{} + } + // construct task options + op := &tasks.ZentaoOptions{ + ConnectionId: connection.ID, + } + + scopeType := strings.Split(bpScope.Id, `/`)[0] + scopeId := strings.Split(bpScope.Id, `/`)[1] + if scopeType == `project` { + scope := &models.ZentaoProject{} + // get repo from db + err = basicRes.GetDal().First(scope, dal.Where(`connection_id = ? AND id = ?`, connection.ID, scopeId)) + if err != nil { + return nil, nil, errors.Default.Wrap(err, fmt.Sprintf("fail to find zentao project %s", bpScope.Id)) + } + op.ProjectId = scope.Id + + if utils.StringsContains(bpScope.Entities, plugin.DOMAIN_TYPE_TICKET) { + scopeTicket := &ticket.Board{ + DomainEntity: domainlayer.DomainEntity{ + Id: didgen.NewDomainIdGenerator(&models.ZentaoProject{}).Generate(connection.ID, scope.Id), + }, + Name: scope.Name, + Type: scope.Type, + } + domainScopes = append(domainScopes, scopeTicket) + } + } else { + scope := &models.ZentaoProduct{} + // get repo from db + err = basicRes.GetDal().First(scope, dal.Where(`connection_id = ? AND id = ?`, connection.ID, scopeId)) + if err != nil { + return nil, nil, errors.Default.Wrap(err, fmt.Sprintf("fail to find zentao product %s", bpScope.Id)) + } + op.ProductId = scope.Id + + if utils.StringsContains(bpScope.Entities, plugin.DOMAIN_TYPE_TICKET) { + scopeTicket := &ticket.Board{ + DomainEntity: domainlayer.DomainEntity{ + Id: didgen.NewDomainIdGenerator(&models.ZentaoProduct{}).Generate(connection.ID, scope.Id), + }, + Name: scope.Name, + Type: scope.Type, + } + domainScopes = append(domainScopes, scopeTicket) + } + } + + if syncPolicy.TimeAfter != nil { + op.TimeAfter = syncPolicy.TimeAfter.Format(time.RFC3339) + } + options, err := tasks.EncodeTaskOptions(op) + if err != nil { + return nil, nil, err + } + + subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, bpScope.Entities) + if err != nil { + return nil, nil, err + } + stage = append(stage, &plugin.PipelineTask{ + Plugin: "zentao", + Subtasks: subtasks, + Options: options, + }) + if err != nil { + return nil, nil, err + } + + plan[i] = stage + } + return plan, domainScopes, nil +} diff --git a/backend/plugins/zentao/api/init.go b/backend/plugins/zentao/api/init.go index d92c2b334..8108249bc 100644 --- a/backend/plugins/zentao/api/init.go +++ b/backend/plugins/zentao/api/init.go @@ -20,11 +20,23 @@ package api import ( "github.com/apache/incubator-devlake/core/context" "github.com/apache/incubator-devlake/helpers/pluginhelper/api" + "github.com/apache/incubator-devlake/plugins/zentao/models" "github.com/go-playground/validator/v10" ) +type MixScopes struct { + ZentaoProduct *models.ZentaoProduct `json:"product"` + ZentaoProject *models.ZentaoProject `json:"project"` +} +type NoTransformation struct{} + var vld *validator.Validate var connectionHelper *api.ConnectionApiHelper +var productScopeHelper *api.ScopeApiHelper[models.ZentaoConnection, models.ZentaoProduct, NoTransformation] +var projectScopeHelper *api.ScopeApiHelper[models.ZentaoConnection, models.ZentaoProject, NoTransformation] + +var productRemoteHelper *api.RemoteApiHelper[models.ZentaoConnection, models.ZentaoProduct, models.ZentaoProductRes, api.BaseRemoteGroupResponse] +var projectRemoteHelper *api.RemoteApiHelper[models.ZentaoConnection, models.ZentaoProject, models.ZentaoProject, api.NoRemoteGroupResponse] var basicRes context.BasicRes func Init(br context.BasicRes) { @@ -34,4 +46,24 @@ func Init(br context.BasicRes) { basicRes, vld, ) + productScopeHelper = api.NewScopeHelper[models.ZentaoConnection, models.ZentaoProduct, NoTransformation]( + basicRes, + vld, + connectionHelper, + ) + projectScopeHelper = api.NewScopeHelper[models.ZentaoConnection, models.ZentaoProject, NoTransformation]( + basicRes, + vld, + connectionHelper, + ) + productRemoteHelper = api.NewRemoteHelper[models.ZentaoConnection, models.ZentaoProduct, models.ZentaoProductRes, api.BaseRemoteGroupResponse]( + basicRes, + vld, + connectionHelper, + ) + projectRemoteHelper = api.NewRemoteHelper[models.ZentaoConnection, models.ZentaoProject, models.ZentaoProject, api.NoRemoteGroupResponse]( + basicRes, + vld, + connectionHelper, + ) } diff --git a/backend/plugins/zentao/api/remote.go b/backend/plugins/zentao/api/remote.go new file mode 100644 index 000000000..9152f5b18 --- /dev/null +++ b/backend/plugins/zentao/api/remote.go @@ -0,0 +1,138 @@ +/* +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" + "fmt" + "net/url" + + context2 "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/zentao/models" +) + +type ProductResponse struct { + Limit int `json:"limit"` + Page int `json:"page"` + Total int `json:"total"` + Values []models.ZentaoProductRes `json:"products"` +} + +type ProjectResponse struct { + Limit int `json:"limit"` + Page int `json:"page"` + Total int `json:"total"` + Values []models.ZentaoProject `json:"projects"` +} + +func getGroup(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.ZentaoConnection) ([]api.BaseRemoteGroupResponse, errors.Error) { + return []api.BaseRemoteGroupResponse{ + { + Id: `products`, + Name: `Products`, + }, + { + Id: `projects`, + Name: `Projects`, + }, + }, nil +} + +// RemoteScopes list all available scope for users +// @Summary list all available scope for users +// @Description list all available scope for users +// @Tags plugins/zentao +// @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/zentao/connections/{connectionId}/remote-scopes [GET] +func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + groupId, ok := input.Query["groupId"] + if !ok || len(groupId) == 0 { + groupId = []string{""} + } + gid := groupId[0] + if gid == "" { + return productRemoteHelper.GetScopesFromRemote(input, getGroup, nil) + } else if gid == `products` { + return productRemoteHelper.GetScopesFromRemote(input, + nil, + func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.ZentaoConnection) ([]models.ZentaoProductRes, errors.Error) { + query := initialQuery(queryData) + // create api client + apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection) + if err != nil { + return nil, err + } + + query.Set("sort", "name") + // list projects part + res, err := apiClient.Get("/products", query, nil) + if err != nil { + return nil, err + } + + resBody := &ProductResponse{} + err = api.UnmarshalResponse(res, resBody) + if err != nil { + return nil, err + } + return resBody.Values, nil + }) + } else if gid == `projects` { + return projectRemoteHelper.GetScopesFromRemote(input, + nil, + func(basicRes context2.BasicRes, gid string, queryData *plugin.QueryData, connection models.ZentaoConnection) ([]models.ZentaoProject, errors.Error) { + query := initialQuery(queryData) + // create api client + apiClient, err := api.NewApiClientFromConnection(context.TODO(), basicRes, &connection) + if err != nil { + return nil, err + } + + query.Set("sort", "name") + // list projects part + res, err := apiClient.Get("/projects", query, nil) + if err != nil { + return nil, err + } + + resBody := &ProjectResponse{} + err = api.UnmarshalResponse(res, resBody) + if err != nil { + return nil, err + } + return resBody.Values, nil + }) + } + return nil, nil +} + +func initialQuery(queryData *plugin.QueryData) url.Values { + query := url.Values{} + query.Set("page", fmt.Sprintf("%v", queryData.Page)) + query.Set("limit", fmt.Sprintf("%v", queryData.PerPage)) + return query +} diff --git a/backend/plugins/zentao/api/scope.go b/backend/plugins/zentao/api/scope.go new file mode 100644 index 000000000..974ec8ec0 --- /dev/null +++ b/backend/plugins/zentao/api/scope.go @@ -0,0 +1,131 @@ +/* +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 ( + "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/zentao/models" +) + +type ProductScopeRes struct { + models.ZentaoProduct + TransformationRuleName string `json:"transformationRuleName,omitempty"` +} + +type ProductScopeReq api.ScopeReq[models.ZentaoProduct] + +type ProjectScopeRes struct { + models.ZentaoProject + TransformationRuleName string `json:"transformationRuleName,omitempty"` +} + +type ProjectScopeReq api.ScopeReq[models.ZentaoProject] + +// PutProductScope create or update zentao products +// @Summary create or update zentao products +// @Description Create or update zentao products +// @Tags plugins/zentao +// @Accept application/json +// @Param connectionId path int true "connection ID" +// @Param scope body ProductScopeReq true "json" +// @Success 200 {object} []models.ZentaoProduct +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/zentao/connections/{connectionId}/product/scopes [PUT] +func PutProductScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return productScopeHelper.Put(input) +} + +// PutProjectScope create or update zentao projects +// @Summary create or update zentao projects +// @Description Create or update zentao projects +// @Tags plugins/zentao +// @Accept application/json +// @Param connectionId path int true "connection ID" +// @Param scope body ProjectScopeReq true "json" +// @Success 200 {object} []models.ZentaoProject +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/zentao/connections/{connectionId}/project/scopes [PUT] +func PutProjectScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return projectScopeHelper.Put(input) +} + +// UpdateProductScope patch to zentao product +// @Summary patch to zentao product +// @Description patch to zentao product +// @Tags plugins/zentao +// @Accept application/json +// @Param connectionId path int true "connection ID" +// @Param scopeId path int true "scope ID" +// @Param scope body models.ZentaoProduct true "json" +// @Success 200 {object} models.ZentaoProduct +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/zentao/connections/{connectionId}/scopes/product/{scopeId} [PATCH] +func UpdateProductScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return productScopeHelper.Update(input, "id") +} + +// UpdateProjectScope patch to zentao project +// @Summary patch to zentao project +// @Description patch to zentao project +// @Tags plugins/zentao +// @Accept application/json +// @Param connectionId path int true "connection ID" +// @Param scopeId path int true "scope ID" +// @Param scope body models.ZentaoProject true "json" +// @Success 200 {object} models.ZentaoProject +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/zentao/connections/{connectionId}/scopes/project/{scopeId} [PATCH] +func UpdateProjectScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return projectScopeHelper.Update(input, "id") +} + +// TODO GetScopeList get zentao projects and products + +// GetProductScope get one product +// @Summary get one product +// @Description get one product +// @Tags plugins/zentao +// @Param connectionId path int true "connection ID" +// @Param scopeId path int true "scope ID" +// @Success 200 {object} ProductScopeRes +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/zentao/connections/{connectionId}/scopes/product/{scopeId} [GET] +func GetProductScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return productScopeHelper.GetScope(input, "id") +} + +// GetProjectScope get one project +// @Summary get one project +// @Description get one project +// @Tags plugins/zentao +// @Param connectionId path int true "connection ID" +// @Param scopeId path int true "scope ID" +// @Success 200 {object} ProjectScopeRes +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/zentao/connections/{connectionId}/scopes/project/{scopeId} [GET] +func GetProjectScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return projectScopeHelper.GetScope(input, "id") +} diff --git a/backend/plugins/zentao/e2e/account_test.go b/backend/plugins/zentao/e2e/account_test.go index b8046cacf..f92104046 100644 --- a/backend/plugins/zentao/e2e/account_test.go +++ b/backend/plugins/zentao/e2e/account_test.go @@ -37,7 +37,6 @@ func TestZentaoAccountDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 1, }, } diff --git a/backend/plugins/zentao/e2e/bug_test.go b/backend/plugins/zentao/e2e/bug_test.go index 44f07e601..4057d29f5 100644 --- a/backend/plugins/zentao/e2e/bug_test.go +++ b/backend/plugins/zentao/e2e/bug_test.go @@ -37,7 +37,6 @@ func TestZentaoBugDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 1, }, } diff --git a/backend/plugins/zentao/e2e/department_test.go b/backend/plugins/zentao/e2e/department_test.go index 7955d7380..34b138296 100644 --- a/backend/plugins/zentao/e2e/department_test.go +++ b/backend/plugins/zentao/e2e/department_test.go @@ -37,7 +37,6 @@ func TestZentaoDepartmentDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 1, }, } diff --git a/backend/plugins/zentao/e2e/execution_test.go b/backend/plugins/zentao/e2e/execution_test.go index 6434576d4..464e3d1ee 100644 --- a/backend/plugins/zentao/e2e/execution_test.go +++ b/backend/plugins/zentao/e2e/execution_test.go @@ -37,7 +37,6 @@ func TestZentaoExecutionDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 1, }, } @@ -53,10 +52,15 @@ func TestZentaoExecutionDataFlow(t *testing.T) { IgnoreTypes: []interface{}{common.NoPKModel{}}, }) - dataflowTester.FlushTabler(&ticket.Board{}) + dataflowTester.FlushTabler(&ticket.Sprint{}) + dataflowTester.FlushTabler(&ticket.BoardSprint{}) dataflowTester.Subtask(tasks.ConvertExecutionMeta, taskData) - dataflowTester.VerifyTableWithOptions(&ticket.Board{}, e2ehelper.TableOptions{ - CSVRelPath: "./snapshot_tables/boards_execution.csv", + dataflowTester.VerifyTableWithOptions(&ticket.Sprint{}, e2ehelper.TableOptions{ + CSVRelPath: "./snapshot_tables/execution_sprint.csv", + IgnoreTypes: []interface{}{common.NoPKModel{}}, + }) + dataflowTester.VerifyTableWithOptions(&ticket.BoardSprint{}, e2ehelper.TableOptions{ + CSVRelPath: "./snapshot_tables/execution_board_sprint.csv", IgnoreTypes: []interface{}{common.NoPKModel{}}, }) } diff --git a/backend/plugins/zentao/e2e/product_test.go b/backend/plugins/zentao/e2e/product_test.go index 9a7831d31..676c29199 100644 --- a/backend/plugins/zentao/e2e/product_test.go +++ b/backend/plugins/zentao/e2e/product_test.go @@ -37,21 +37,11 @@ func TestZentaoProductDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 9, }, } // import raw data table - dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_products.csv", - "_raw_zentao_api_products") - - // verify extraction - dataflowTester.FlushTabler(&models.ZentaoProduct{}) - dataflowTester.Subtask(tasks.ExtractProductMeta, taskData) - dataflowTester.VerifyTableWithOptions(&models.ZentaoProduct{}, e2ehelper.TableOptions{ - CSVRelPath: "./snapshot_tables/_tool_zentao_products.csv", - IgnoreTypes: []interface{}{common.NoPKModel{}}, - }) + dataflowTester.ImportCsvIntoTabler("./snapshot_tables/_tool_zentao_products.csv", &models.ZentaoProduct{}) dataflowTester.FlushTabler(&ticket.Board{}) dataflowTester.Subtask(tasks.ConvertProductMeta, taskData) diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv index f1aaf1229..7500e19a1 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv @@ -1,13 +1,13 @@ id,params,data,url,input,created_at -31,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":10,""dept"":1,""account"":""testManager"",""realname"":""\u6d4b\u8bd5\u7ecf\u7406"",""role"":""qd"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -32,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":9,""dept"":3,""account"":""tester3"",""realname"":""\u6d4b\u8bd5\u4e19"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -33,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":8,""dept"":3,""account"":""tester2"",""realname"":""\u6d4b\u8bd5\u4e59"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -34,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":7,""dept"":3,""account"":""tester1"",""realname"":""\u6d4b\u8bd5\u7532"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -35,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":6,""dept"":2,""account"":""dev3"",""realname"":""\u5f00\u53d1\u4e19"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -36,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":5,""dept"":2,""account"":""dev2"",""realname"":""\u5f00\u53d1\u4e59"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -37,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":4,""dept"":2,""account"":""dev1"",""realname"":""\u5f00\u53d1\u7532"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -38,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":3,""dept"":6,""account"":""projectManager"",""realname"":""\u9879\u76ee\u7ecf\u7406"",""role"":""pm"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -39,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":2,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -40,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":1,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -41,"{""ConnectionId"":3,""ProductId"":2,""ExecutionId"":1,""ProjectId"":3}","{""id"":11,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 -42,"{""ConnectionId"":2,""ProductId"":1,""ExecutionId"":3,""ProjectId"":3}","{""id"":12,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +31,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":10,""dept"":1,""account"":""testManager"",""realname"":""\u6d4b\u8bd5\u7ecf\u7406"",""role"":""qd"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +32,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":9,""dept"":3,""account"":""tester3"",""realname"":""\u6d4b\u8bd5\u4e19"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +33,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":8,""dept"":3,""account"":""tester2"",""realname"":""\u6d4b\u8bd5\u4e59"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +34,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":7,""dept"":3,""account"":""tester1"",""realname"":""\u6d4b\u8bd5\u7532"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +35,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":6,""dept"":2,""account"":""dev3"",""realname"":""\u5f00\u53d1\u4e19"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +36,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":5,""dept"":2,""account"":""dev2"",""realname"":""\u5f00\u53d1\u4e59"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +37,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":4,""dept"":2,""account"":""dev1"",""realname"":""\u5f00\u53d1\u7532"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +38,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":3,""dept"":6,""account"":""projectManager"",""realname"":""\u9879\u76ee\u7ecf\u7406"",""role"":""pm"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +39,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":2,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +40,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":1,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +41,"{""ConnectionId"":3,""ProductId"":2,""ProjectId"":3}","{""id"":11,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 +42,"{""ConnectionId"":2,""ProductId"":1,""ProjectId"":3}","{""id"":12,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970 diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv index d69bb7129..c60979cae 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv @@ -1,7 +1,7 @@ id,params,data,url,input,created_at -1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":4,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":11,""execution"":1,""plan"":0,""story"":4,""storyVersion"":1,""task"":9,""toTask"":0,""toStory"":0,""title"":""\u552e\u540e\u670d\u52a1\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\ [...] -2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":10,""execution"":1,""plan"":0,""story"":3,""storyVersion"":2,""task"":6,""toTask"":0,""toStory"":0,""title"":""\u6210\u679c\u5c55\u793a\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\ [...] -3,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":2,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":9,""execution"":1,""plan"":1,""story"":2,""storyVersion"":1,""task"":15,""toTask"":0,""toStory"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u9875\u9762\u95ee\u9898"",""keywords"":""hh"",""severity"":3,""pri"":2,""type"":""codeerror"",""os"":"",windows"",""browser"":"",chrome"",""hardware"":"""",""found"":"""",""steps"":""\u00 [...] -4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":8,""execution"":1,""plan"":0,""story"":1,""storyVersion"":1,""task"":1,""toTask"":0,""toStory"":0,""title"":""\u9996\u9875\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb\ [...] -5,"{""ConnectionId"":2,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":6,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":9,""execution"":1,""plan"":1,""story"":2,""storyVersion"":1,""task"":15,""toTask"":0,""toStory"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u9875\u9762\u95ee\u9898"",""keywords"":""hh"",""severity"":3,""pri"":2,""type"":""codeerror"",""os"":"",windows"",""browser"":"",chrome"",""hardware"":"""",""found"":"""",""steps"":""\u00 [...] -6,"{""ConnectionId"":3,""ProductId"":3,""ExecutionId"":11,""ProjectId"":1}","{""id"":5,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":8,""execution"":1,""plan"":0,""story"":1,""storyVersion"":1,""task"":1,""toTask"":0,""toStory"":0,""title"":""\u9996\u9875\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb [...] +1,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":4,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":11,""execution"":1,""plan"":0,""story"":4,""storyVersion"":1,""task"":9,""toTask"":0,""toStory"":0,""title"":""\u552e\u540e\u670d\u52a1\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb\u5165 [...] +2,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":3,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":10,""execution"":1,""plan"":0,""story"":3,""storyVersion"":2,""task"":6,""toTask"":0,""toStory"":0,""title"":""\u6210\u679c\u5c55\u793a\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb\u5165 [...] +3,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":2,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":9,""execution"":1,""plan"":1,""story"":2,""storyVersion"":1,""task"":15,""toTask"":0,""toStory"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u9875\u9762\u95ee\u9898"",""keywords"":""hh"",""severity"":3,""pri"":2,""type"":""codeerror"",""os"":"",windows"",""browser"":"",chrome"",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u [...] +4,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":1,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":8,""execution"":1,""plan"":0,""story"":1,""storyVersion"":1,""task"":1,""toTask"":0,""toStory"":0,""title"":""\u9996\u9875\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb\u5165\u9996\u9875\ [...] +5,"{""ConnectionId"":2,""ProductId"":3,""ProjectId"":1}","{""id"":6,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":9,""execution"":1,""plan"":1,""story"":2,""storyVersion"":1,""task"":15,""toTask"":0,""toStory"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u9875\u9762\u95ee\u9898"",""keywords"":""hh"",""severity"":3,""pri"":2,""type"":""codeerror"",""os"":"",windows"",""browser"":"",chrome"",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u [...] +6,"{""ConnectionId"":3,""ProductId"":3,""ProjectId"":1}","{""id"":5,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":8,""execution"":1,""plan"":0,""story"":1,""storyVersion"":1,""task"":1,""toTask"":0,""toStory"":0,""title"":""\u9996\u9875\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb\u5165\u9996\u9875\ [...] diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv index 88bd599d5..e7738528d 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv @@ -1,13 +1,13 @@ id,params,data,url,input,created_at -31,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":10,""dept"":1,""account"":""testManager"",""realname"":""\u6d4b\u8bd5\u7ecf\u7406"",""role"":""qd"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -32,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":9,""dept"":3,""account"":""tester3"",""realname"":""\u6d4b\u8bd5\u4e19"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -33,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":8,""dept"":3,""account"":""tester2"",""realname"":""\u6d4b\u8bd5\u4e59"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -34,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":7,""dept"":3,""account"":""tester1"",""realname"":""\u6d4b\u8bd5\u7532"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -35,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":6,""dept"":2,""account"":""dev3"",""realname"":""\u5f00\u53d1\u4e19"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -36,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":5,""dept"":2,""account"":""dev2"",""realname"":""\u5f00\u53d1\u4e59"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -37,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":4,""dept"":2,""account"":""dev1"",""realname"":""\u5f00\u53d1\u7532"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -38,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":3,""dept"":6,""account"":""projectManager"",""realname"":""\u9879\u76ee\u7ecf\u7406"",""role"":""pm"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -39,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":2,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -40,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":1,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -41,"{""ConnectionId"":2,""ProductId"":1,""ExecutionId"":4,""ProjectId"":3}","{""id"":12,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 -42,"{""ConnectionId"":3,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":11,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +31,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":10,""dept"":1,""account"":""testManager"",""realname"":""\u6d4b\u8bd5\u7ecf\u7406"",""role"":""qd"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +32,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":9,""dept"":3,""account"":""tester3"",""realname"":""\u6d4b\u8bd5\u4e19"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +33,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":8,""dept"":3,""account"":""tester2"",""realname"":""\u6d4b\u8bd5\u4e59"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +34,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":7,""dept"":3,""account"":""tester1"",""realname"":""\u6d4b\u8bd5\u7532"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +35,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":6,""dept"":2,""account"":""dev3"",""realname"":""\u5f00\u53d1\u4e19"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +36,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":5,""dept"":2,""account"":""dev2"",""realname"":""\u5f00\u53d1\u4e59"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +37,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":4,""dept"":2,""account"":""dev1"",""realname"":""\u5f00\u53d1\u7532"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +38,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":3,""dept"":6,""account"":""projectManager"",""realname"":""\u9879\u76ee\u7ecf\u7406"",""role"":""pm"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +39,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":2,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +40,"{""ConnectionId"":1,""ProductId"":1,""ProjectId"":3}","{""id"":1,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +41,"{""ConnectionId"":2,""ProductId"":1,""ProjectId"":3}","{""id"":12,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 +42,"{""ConnectionId"":3,""ProductId"":1,""ProjectId"":3}","{""id"":11,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124 diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv index 500d4a4c9..c72755db1 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv @@ -1,4 +1,4 @@ id,params,data,url,input,created_at -1,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":12,""ProjectId"":1}","{""id"":12,""project"":1091,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":1091,""path"":"",1091,12,"",""grade"":1,""name"":""TR5"",""code"":""0.1.3"",""begin"":""2022-11-01"",""end"":""2022-11-03"",""realBegan"":""2022-07-07"",""realEnd"":null,""days"":"""",""status"": [...] -2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":7,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":7,""path"":"",7,1,"",""grade"":1,""name"":""\u4f01\u4e1a\u7f51\u7ad9\u7b2c\u4e00\u671f"",""code"":""coWeb1"",""begin"":""2022-05-01"",""end"":""2022-06-01"",""realBegan"":null,""realEnd"":null,""days" [...] -3,"{""ConnectionId"":2,""ProductId"":4,""ExecutionId"":1,""ProjectId"":1}","{""id"":11,""project"":7,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":7,""path"":"",7,1,"",""grade"":1,""name"":""\u4f01\u4e1a\u7f51\u7ad9\u7b2c\u4e00\u671f"",""code"":""coWeb1"",""begin"":""2022-05-01"",""end"":""2022-06-01"",""realBegan"":null,""realEnd"":null,""days [...] +1,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":12,""project"":1,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":1091,""path"":"",1091,12,"",""grade"":1,""name"":""TR5"",""code"":""0.1.3"",""begin"":""2022-11-01"",""end"":""2022-11-03"",""realBegan"":""2022-07-07"",""realEnd"":null,""days"":"""",""status"":""done"",""subStatus"" [...] +2,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":1,""project"":1,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":7,""path"":"",7,1,"",""grade"":1,""name"":""\u4f01\u4e1a\u7f51\u7ad9\u7b2c\u4e00\u671f"",""code"":""coWeb1"",""begin"":""2022-05-01"",""end"":""2022-06-01"",""realBegan"":null,""realEnd"":null,""days"":20,""status"":"" [...] +3,"{""ConnectionId"":2,""ProductId"":4,""ProjectId"":1}","{""id"":11,""project"":1,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":7,""path"":"",7,1,"",""grade"":1,""name"":""\u4f01\u4e1a\u7f51\u7ad9\u7b2c\u4e00\u671f"",""code"":""coWeb1"",""begin"":""2022-05-01"",""end"":""2022-06-01"",""realBegan"":null,""realEnd"":null,""days"":20,""status"":" [...] diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions_real.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions_real.csv deleted file mode 100644 index 9c755cee3..000000000 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions_real.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,params,data,url,input,created_at -1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":1091,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":1091,""path"":"",1091,12,"",""grade"":1,""name"":""TR5"",""code"":""0.1.3"",""begin"":""2022-11-01"",""end"":""2022-11-03"",""realBegan"":""2022-07-07"",""realEnd"":null,""days"":0,""status"":""don [...] diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv index c8b7dc499..7e749c20a 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv @@ -1,3 +1,3 @@ id,params,data,url,input,created_at -4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":9,""ProjectId"":1}","{""id"":3,""program"":10,""name"":""\u4ea7\u54c1\u540d\u79f01"",""code"":""\u4ea7\u54c1\u4ee3\u53f72"",""bind"":""0"",""line"":31,""type"":""normal"",""status"":""normal"",""subStatus"":"""",""desc"":""\u003Cspan style=\""background-color:#FFFFFF;\""\u003E\u4ea7\u54c1\u63cf\u8ff01\u003C\/span\u003E"",""PO"":{""id"":1,""account"":""devlake"",""avatar"":"""",""realname"":""devlake""},""QD"":{""id"":1,""account"":"" [...] -5,"{""ConnectionId"":2,""ProductId"":2,""ExecutionId"":9,""ProjectId"":1}","{""id"":3,""program"":10,""name"":""\u4ea7\u54c1\u540d\u79f01"",""code"":""\u4ea7\u54c1\u4ee3\u53f72"",""bind"":""0"",""line"":31,""type"":""normal"",""status"":""normal"",""subStatus"":"""",""desc"":""\u003Cspan style=\""background-color:#FFFFFF;\""\u003E\u4ea7\u54c1\u63cf\u8ff01\u003C\/span\u003E"",""PO"":{""id"":1,""account"":""devlake"",""avatar"":"""",""realname"":""devlake""},""QD"":{""id"":1,""account"":"" [...] +4,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":3,""program"":10,""name"":""\u4ea7\u54c1\u540d\u79f01"",""code"":""\u4ea7\u54c1\u4ee3\u53f72"",""bind"":""0"",""line"":31,""type"":""normal"",""status"":""normal"",""subStatus"":"""",""desc"":""\u003Cspan style=\""background-color:#FFFFFF;\""\u003E\u4ea7\u54c1\u63cf\u8ff01\u003C\/span\u003E"",""PO"":{""id"":1,""account"":""devlake"",""avatar"":"""",""realname"":""devlake""},""QD"":{""id"":1,""account"":""devlake"",""avatar [...] +5,"{""ConnectionId"":2,""ProductId"":2,""ProjectId"":1}","{""id"":3,""program"":10,""name"":""\u4ea7\u54c1\u540d\u79f01"",""code"":""\u4ea7\u54c1\u4ee3\u53f72"",""bind"":""0"",""line"":31,""type"":""normal"",""status"":""normal"",""subStatus"":"""",""desc"":""\u003Cspan style=\""background-color:#FFFFFF;\""\u003E\u4ea7\u54c1\u63cf\u8ff01\u003C\/span\u003E"",""PO"":{""id"":1,""account"":""devlake"",""avatar"":"""",""realname"":""devlake""},""QD"":{""id"":1,""account"":""devlake"",""avatar [...] diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv index 78fb04a5f..f5a929977 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv @@ -1,10 +1,10 @@ id,params,data,url,input,created_at -1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":7,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":7,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u5173\u4e8e\u6211\u4eec\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedB [...] -2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":6,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":6,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u5408\u4f5c\u6d3d\u8c08\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedB [...] -3,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":5,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":5,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u8bda\u8058\u82f1\u624d\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedB [...] -4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":4,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":4,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u552e\u540e\u670d\u52a1\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developed"",""stagedBy [...] -5,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":3,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u6210\u679c\u5c55\u793a\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":0,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedB [...] -6,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":2,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":2,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1\u3002"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""projected"",""st [...] -7,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":1,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u9996\u9875\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto" [...] -8,"{""ConnectionId"":2,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":8,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":2,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1\u3002"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""projected"",""st [...] -9,"{""ConnectionId"":3,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":9,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":1,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u9996\u9875\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto" [...] +1,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":7,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":7,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u5173\u4e8e\u6211\u4eec\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedBy"":"""",""mailto" [...] +2,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":6,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":6,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u5408\u4f5c\u6d3d\u8c08\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedBy"":"""",""mailto" [...] +3,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":5,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":5,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u8bda\u8058\u82f1\u624d\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedBy"":"""",""mailto" [...] +4,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":4,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":4,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u552e\u540e\u670d\u52a1\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developed"",""stagedBy"":"""",""mailto"" [...] +5,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":3,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":3,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u6210\u679c\u5c55\u793a\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":0,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto" [...] +6,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":2,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":2,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1\u3002"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""projected"",""stagedBy"":"""",""ma [...] +7,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":1,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":1,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u9996\u9875\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto"":[],""lib"":0,""f [...] +8,"{""ConnectionId"":2,""ProductId"":3,""ProjectId"":1}","{""id"":8,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":2,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1\u3002"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""projected"",""stagedBy"":"""",""ma [...] +9,"{""ConnectionId"":3,""ProductId"":3,""ProjectId"":1}","{""id"":9,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":1,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u9996\u9875\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto"":[],""lib"":0,""f [...] diff --git a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv index 5b9361a62..f0640a817 100644 --- a/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv +++ b/backend/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv @@ -1,6 +1,6 @@ id,params,data,url,input,created_at -1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":13,""parent"":0,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":0,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":"" [...] -2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":2,""project"":13,""parent"":0,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":12.1,""consumed"":2.1,""left"":10,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""accoun [...] -4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""project"":13,""parent"":-1,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":11.2,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account" [...] -5,"{""ConnectionId"":3,""ProductId"":1,""ExecutionId"":4,""ProjectId"":1}","{""id"":2,""project"":13,""parent"":0,""execution"":9,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":12.1,""consumed"":2.1,""left"":10,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""accoun [...] -6,"{""ConnectionId"":2,""ProductId"":1,""ExecutionId"":3,""ProjectId"":1}","{""id"":3,""project"":13,""parent"":-1,""execution"":9,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":11.2,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account" [...] +1,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":1,""project"":1,""parent"":0,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":0,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":""productManager"","" [...] +2,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":2,""project"":1,""parent"":0,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":12.1,""consumed"":2.1,""left"":10,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":""productManage [...] +4,"{""ConnectionId"":1,""ProductId"":3,""ProjectId"":1}","{""id"":3,""project"":1,""parent"":-1,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":11.2,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":""productManager" [...] +5,"{""ConnectionId"":3,""ProductId"":1,""ProjectId"":1}","{""id"":2,""project"":1,""parent"":0,""execution"":9,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":12.1,""consumed"":2.1,""left"":10,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":""productManage [...] +6,"{""ConnectionId"":2,""ProductId"":1,""ProjectId"":1}","{""id"":3,""project"":1,""parent"":-1,""execution"":9,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":11.2,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":""productManager" [...] diff --git a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv index 4a5f7f367..db4ccd6f4 100644 --- a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv +++ b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv @@ -1,2 +1,3 @@ connection_id,id,project,model,type,lifetime,budget,budget_unit,attribute,percent,milestone,output,auth,parent,path,grade,name,code,plan_begin,plan_end,real_began,real_end,status,sub_status,pri,description,version,parent_version,plan_duration,real_duration,opened_by_id,opened_date,opened_version,last_edited_by_id,last_edited_date,closed_by_id,closed_date,canceled_by_id,canceled_date,suspended_date,po_id,pm_id,qd_id,rd_id,team,acl,order_in,vision,display_cards,fluid_board,deleted,total_ho [...] -1,1,7,,sprint,short,0,CNY,,0,0,,,7,",7,1,",1,企业网站第一期,coWeb1,2022-05-01T00:00:00.000+00:00,2022-06-01T00:00:00.000+00:00,,,doing,,1,开发企业网站的基本雏形。<br />,0,0,0,0,0,,,1,2022-11-21T06:00:56.000+00:00,0,,0,,,2,3,10,2,公司开发团队,open,5,rnd,0,0,0,11753,52,51.5,27.5,0,65.2,0 +1,1,1,,sprint,short,0,CNY,,0,0,,,7,",7,1,",1,企业网站第一期,coWeb1,2022-05-01T00:00:00.000+00:00,2022-06-01T00:00:00.000+00:00,,,doing,,1,开发企业网站的基本雏形。<br />,0,0,0,0,0,,,1,2022-11-21T06:00:56.000+00:00,0,,0,,,2,3,10,2,公司开发团队,open,5,rnd,0,0,0,11753,52,51.5,27.5,1,65.2,0 +1,12,1,,sprint,short,0,CNY,,0,0,,,1091,",1091,12,",1,TR5,0.1.3,2022-11-01T00:00:00.000+00:00,2022-11-03T00:00:00.000+00:00,2022-07-07T00:00:00.000+00:00,,done,,1,,0,0,24,0,6,2021-05-27T07:16:59.000+00:00,15.0.rc3,6,2022-11-15T08:22:09.000+00:00,0,,0,,,0,6,0,0,Windows组,open,5,rnd,0,1,0,0,8411,11564.5,0,1,100,0 diff --git a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv index 5680ee342..40337d85f 100644 --- a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv +++ b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv @@ -1,4 +1,4 @@ connection_id,id,project,parent,execution,module,design,story,story_version,design_version,from_bug,feedback,from_issue,name,type,mode,pri,estimate,consumed,deadline,status,sub_status,color,description,version,opened_by_id,opened_by_name,opened_date,assigned_to_id,assigned_to_name,assigned_date,est_started,real_started,finished_id,finished_date,finished_list,canceled_id,canceled_date,closed_by_id,closed_date,plan_duration,real_duration,closed_reason,last_edited_id,last_edited_date,activa [...] -1,1,13,0,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,0,0,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,21.11 -1,2,13,0,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,12.1,2.1,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,3 -1,3,13,-1,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,11.2,0,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,43.22121 +1,1,1,0,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,0,0,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,21.11 +1,2,1,0,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,12.1,2.1,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,3 +1,3,1,-1,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,11.2,0,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,43.22121 diff --git a/backend/plugins/zentao/e2e/snapshot_tables/boards_execution.csv b/backend/plugins/zentao/e2e/snapshot_tables/boards_execution.csv deleted file mode 100644 index e596c62f9..000000000 --- a/backend/plugins/zentao/e2e/snapshot_tables/boards_execution.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,name,description,url,created_date,type -zentao:ZentaoExecution:1:1,企业网站第一期,开发企业网站的基本雏形。<br />,",7,1,",,sprint diff --git a/backend/plugins/zentao/e2e/snapshot_tables/execution_board_sprint.csv b/backend/plugins/zentao/e2e/snapshot_tables/execution_board_sprint.csv new file mode 100644 index 000000000..0760d8741 --- /dev/null +++ b/backend/plugins/zentao/e2e/snapshot_tables/execution_board_sprint.csv @@ -0,0 +1,3 @@ +board_id,sprint_id +zentao:ZentaoProject:1:1,zentao:ZentaoExecution:1:1 +zentao:ZentaoProject:1:12,zentao:ZentaoExecution:1:12 diff --git a/backend/plugins/zentao/e2e/snapshot_tables/execution_sprint.csv b/backend/plugins/zentao/e2e/snapshot_tables/execution_sprint.csv new file mode 100644 index 000000000..81e23be75 --- /dev/null +++ b/backend/plugins/zentao/e2e/snapshot_tables/execution_sprint.csv @@ -0,0 +1,3 @@ +id,name,url,status,started_date,ended_date,completed_date,original_board_id +zentao:ZentaoExecution:1:1,企业网站第一期,",7,1,",ACTIVE,,,,zentao:ZentaoProject:1:1 +zentao:ZentaoExecution:1:12,TR5,",1091,12,",CLOSED,2022-07-07T00:00:00.000+00:00,,,zentao:ZentaoProject:1:12 diff --git a/backend/plugins/zentao/e2e/story_test.go b/backend/plugins/zentao/e2e/story_test.go index c14695459..14ca04a4f 100644 --- a/backend/plugins/zentao/e2e/story_test.go +++ b/backend/plugins/zentao/e2e/story_test.go @@ -37,7 +37,6 @@ func TestZentaoStoryDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 1, }, } diff --git a/backend/plugins/zentao/e2e/task_test.go b/backend/plugins/zentao/e2e/task_test.go index 1debcdf2b..a7967570a 100644 --- a/backend/plugins/zentao/e2e/task_test.go +++ b/backend/plugins/zentao/e2e/task_test.go @@ -37,7 +37,6 @@ func TestZentaoTaskDataFlow(t *testing.T) { ConnectionId: 1, ProjectId: 1, ProductId: 3, - ExecutionId: 1, }, } diff --git a/backend/plugins/zentao/impl/impl.go b/backend/plugins/zentao/impl/impl.go index 66359d068..617bf1fb1 100644 --- a/backend/plugins/zentao/impl/impl.go +++ b/backend/plugins/zentao/impl/impl.go @@ -34,7 +34,8 @@ var _ plugin.PluginMeta = (*Zentao)(nil) var _ plugin.PluginInit = (*Zentao)(nil) var _ plugin.PluginTask = (*Zentao)(nil) var _ plugin.PluginApi = (*Zentao)(nil) -var _ plugin.PluginBlueprintV100 = (*Zentao)(nil) + +// var _ plugin.CompositePluginBlueprintV200 = (*Zentao)(nil) var _ plugin.CloseablePluginTask = (*Zentao)(nil) type Zentao struct{} @@ -50,9 +51,8 @@ func (p Zentao) Init(basicRes context.BasicRes) errors.Error { func (p Zentao) SubTaskMetas() []plugin.SubTaskMeta { return []plugin.SubTaskMeta{ - tasks.CollectProductMeta, - tasks.ExtractProductMeta, tasks.ConvertProductMeta, + tasks.ConvertProjectMeta, tasks.CollectExecutionMeta, tasks.ExtractExecutionMeta, tasks.ConvertExecutionMeta, @@ -123,11 +123,28 @@ func (p Zentao) ApiResources() map[string]map[string]plugin.ApiResourceHandler { "PATCH": api.PatchConnection, "DELETE": api.DeleteConnection, }, + "connections/:connectionId/product/scopes": { + "PUT": api.PutProductScope, + }, + "connections/:connectionId/project/scopes": { + "PUT": api.PutProjectScope, + }, + "connections/:connectionId/scopes/product/:scopeId": { + "GET": api.GetProductScope, + "PATCH": api.UpdateProductScope, + }, + "connections/:connectionId/scopes/project/:scopeId": { + "GET": api.GetProjectScope, + "PATCH": api.UpdateProjectScope, + }, + "connections/:connectionId/remote-scopes": { + "GET": api.RemoteScopes, + }, } } -func (p Zentao) MakePipelinePlan(connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) { - return api.MakePipelinePlan(p.SubTaskMetas(), connectionId, scope) +func (p Zentao) MakeDataSourcePipelinePlanV200(connectionId uint64, scopes []*plugin.BlueprintScopeV200, syncPolicy plugin.BlueprintSyncPolicy) (pp plugin.PipelinePlan, sc []plugin.Scope, err errors.Error) { + return api.MakeDataSourcePipelinePlanV200(p.SubTaskMetas(), connectionId, scopes, &syncPolicy) } func (p Zentao) Close(taskCtx plugin.TaskContext) errors.Error { diff --git a/backend/plugins/zentao/models/product.go b/backend/plugins/zentao/models/product.go index ced4f0735..5f1ab2af6 100644 --- a/backend/plugins/zentao/models/product.go +++ b/backend/plugins/zentao/models/product.go @@ -18,7 +18,9 @@ limitations under the License. package models import ( + "fmt" "github.com/apache/incubator-devlake/core/models/common" + "github.com/apache/incubator-devlake/core/plugin" helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api" ) @@ -66,41 +68,91 @@ type ZentaoProductRes struct { CaseReview bool `json:"caseReview"` } +func getAccountId(account *ZentaoAccount) int64 { + if account != nil { + return account.ID + } + return 0 +} + +func (res ZentaoProductRes) ConvertApiScope() plugin.ToolLayerScope { + return &ZentaoProduct{ + Id: res.ID, + Program: res.Program, + Name: res.Name, + Code: res.Code, + Bind: res.Bind, + Line: res.Line, + Type: `product/` + res.Type, + Status: res.Status, + SubStatus: res.SubStatus, + Description: res.Description, + POId: getAccountId(res.PO), + QDId: getAccountId(res.QD), + RDId: getAccountId(res.RD), + Acl: res.Acl, + Reviewer: res.Reviewer, + CreatedById: getAccountId(res.CreatedBy), + CreatedDate: res.CreatedDate, + CreatedVersion: res.CreatedVersion, + OrderIn: res.OrderIn, + Deleted: res.Deleted, + Plans: res.Plans, + Releases: res.Releases, + Builds: res.Builds, + Cases: res.Cases, + Projects: res.Projects, + Executions: res.Executions, + Bugs: res.Bugs, + Docs: res.Docs, + Progress: res.Progress, + CaseReview: res.CaseReview, + } +} + type ZentaoProduct struct { - ConnectionId uint64 `gorm:"primaryKey;type:BIGINT NOT NULL"` - Id int64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"` - Program int `json:"program"` - Name string `json:"name"` - Code string `json:"code"` - Bind string `json:"bind"` - Line int `json:"line"` - Type string `json:"type"` - Status string `json:"status"` - SubStatus string `json:"subStatus"` - Description string `json:"desc"` - POId int64 - QDId int64 - RDId int64 - Acl string `json:"acl"` - Reviewer string `json:"reviewer"` - CreatedById int64 - CreatedDate *helper.Iso8601Time `json:"createdDate"` - CreatedVersion string `json:"createdVersion"` - OrderIn int `json:"order"` - Deleted string `json:"deleted"` - Plans int `json:"plans"` - Releases int `json:"releases"` - Builds int `json:"builds"` - Cases int `json:"cases"` - Projects int `json:"projects"` - Executions int `json:"executions"` - Bugs int `json:"bugs"` - Docs int `json:"docs"` - Progress float64 `json:"progress"` - CaseReview bool `json:"caseReview"` - common.NoPKModel + common.NoPKModel `json:"-"` + ConnectionId uint64 `json:"connectionid" gorm:"primaryKey;type:BIGINT NOT NULL"` + Id int64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"` + Program int `json:"program"` + Name string `json:"name"` + Code string `json:"code"` + Bind string `json:"bind"` + Line int `json:"line"` + Type string `json:"type"` + Status string `json:"status"` + SubStatus string `json:"subStatus"` + Description string `json:"desc"` + POId int64 + QDId int64 + RDId int64 + Acl string `json:"acl"` + Reviewer string `json:"reviewer"` + CreatedById int64 + CreatedDate *helper.Iso8601Time `json:"createdDate"` + CreatedVersion string `json:"createdVersion"` + OrderIn int `json:"order"` + Deleted string `json:"deleted"` + Plans int `json:"plans"` + Releases int `json:"releases"` + Builds int `json:"builds"` + Cases int `json:"cases"` + Projects int `json:"projects"` + Executions int `json:"executions"` + Bugs int `json:"bugs"` + Docs int `json:"docs"` + Progress float64 `json:"progress"` + CaseReview bool `json:"caseReview"` } func (ZentaoProduct) TableName() string { return "_tool_zentao_products" } + +func (p ZentaoProduct) ScopeId() string { + return fmt.Sprintf(`product/%d`, p.Id) +} + +func (p ZentaoProduct) ScopeName() string { + return p.Name +} diff --git a/backend/plugins/zentao/models/project.go b/backend/plugins/zentao/models/project.go index 89945a8f5..0c4d3821f 100644 --- a/backend/plugins/zentao/models/project.go +++ b/backend/plugins/zentao/models/project.go @@ -18,47 +18,49 @@ limitations under the License. package models import ( + "fmt" "github.com/apache/incubator-devlake/core/models/common" + "github.com/apache/incubator-devlake/core/plugin" helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api" ) type ZentaoProject struct { - common.NoPKModel - ConnectionId uint64 `gorm:"primaryKey;type:BIGINT NOT NULL"` - ID int64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"` - Project int64 `json:"project"` - Model string `json:"model"` - Type string `json:"type"` - Lifetime string `json:"lifetime"` - Budget string `json:"budget"` - BudgetUnit string `json:"budgetUnit"` - Attribute string `json:"attribute"` - Percent int `json:"percent"` - Milestone string `json:"milestone"` - Output string `json:"output"` - Auth string `json:"auth"` - Parent int64 `json:"parent"` - Path string `json:"path"` - Grade int `json:"grade"` - Name string `json:"name"` - Code string `json:"code"` - PlanBegin *helper.Iso8601Time `json:"begin"` - PlanEnd *helper.Iso8601Time `json:"end"` - RealBegan *helper.Iso8601Time `json:"realBegan"` - RealEnd *helper.Iso8601Time `json:"realEnd"` - Days int `json:"days"` - Status string `json:"status"` - SubStatus string `json:"subStatus"` - Pri string `json:"pri"` - Description string `json:"desc"` - Version int `json:"version"` - ParentVersion int `json:"parentVersion"` - PlanDuration int `json:"planDuration"` - RealDuration int `json:"realDuration"` + common.NoPKModel `json:"-"` + ConnectionId uint64 `json:"connectionid" gorm:"primaryKey;type:BIGINT NOT NULL"` + Id int64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"` + Project int64 `json:"project"` + Model string `json:"model"` + Type string `json:"type"` + Lifetime string `json:"lifetime"` + Budget string `json:"budget"` + BudgetUnit string `json:"budgetUnit"` + Attribute string `json:"attribute"` + Percent int `json:"percent"` + Milestone string `json:"milestone"` + Output string `json:"output"` + Auth string `json:"auth"` + Parent int64 `json:"parent"` + Path string `json:"path"` + Grade int `json:"grade"` + Name string `json:"name"` + Code string `json:"code"` + PlanBegin *helper.Iso8601Time `json:"begin"` + PlanEnd *helper.Iso8601Time `json:"end"` + RealBegan *helper.Iso8601Time `json:"realBegan"` + RealEnd *helper.Iso8601Time `json:"realEnd"` + Days int `json:"days"` + Status string `json:"status"` + SubStatus string `json:"subStatus"` + Pri string `json:"pri"` + Description string `json:"desc"` + Version int `json:"version"` + ParentVersion int `json:"parentVersion"` + PlanDuration int `json:"planDuration"` + RealDuration int `json:"realDuration"` //OpenedBy string `json:"openedBy"` - OpenedDate *helper.Iso8601Time `json:"openedDate"` - OpenedVersion string `json:"openedVersion"` - LastEditedBy string `json:"lastEditedBy"` + OpenedDate *helper.Iso8601Time `json:"openedDate"` + OpenedVersion string `json:"openedVersion"` + //LastEditedBy string `json:"lastEditedBy"` LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"` ClosedBy string `json:"closedBy"` ClosedDate *helper.Iso8601Time `json:"closedDate"` @@ -111,3 +113,15 @@ type Hours struct { func (ZentaoProject) TableName() string { return "_tool_zentao_projects" } + +func (p ZentaoProject) ScopeId() string { + return fmt.Sprintf(`project/%d`, p.Id) +} + +func (p ZentaoProject) ScopeName() string { + return p.Name +} + +func (p ZentaoProject) ConvertApiScope() plugin.ToolLayerScope { + return p +} diff --git a/backend/plugins/zentao/tasks/account_collector.go b/backend/plugins/zentao/tasks/account_collector.go index 176073cc5..7294f449f 100644 --- a/backend/plugins/zentao/tasks/account_collector.go +++ b/backend/plugins/zentao/tasks/account_collector.go @@ -39,7 +39,6 @@ func CollectAccount(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_ACCOUNT_TABLE, @@ -77,4 +76,5 @@ var CollectAccountMeta = plugin.SubTaskMeta{ EntryPoint: CollectAccount, EnabledByDefault: true, Description: "Collect Account data from Zentao api", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } diff --git a/backend/plugins/zentao/tasks/account_convertor.go b/backend/plugins/zentao/tasks/account_convertor.go index eb7b44e15..96b33d417 100644 --- a/backend/plugins/zentao/tasks/account_convertor.go +++ b/backend/plugins/zentao/tasks/account_convertor.go @@ -60,7 +60,6 @@ func ConvertAccount(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_ACCOUNT_TABLE, diff --git a/backend/plugins/zentao/tasks/account_extractor.go b/backend/plugins/zentao/tasks/account_extractor.go index 939c62bc7..476cdb8cf 100644 --- a/backend/plugins/zentao/tasks/account_extractor.go +++ b/backend/plugins/zentao/tasks/account_extractor.go @@ -43,7 +43,6 @@ func ExtractAccount(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_ACCOUNT_TABLE, diff --git a/backend/plugins/zentao/tasks/bug_collector.go b/backend/plugins/zentao/tasks/bug_collector.go index 0b8ca0255..91758442b 100644 --- a/backend/plugins/zentao/tasks/bug_collector.go +++ b/backend/plugins/zentao/tasks/bug_collector.go @@ -33,13 +33,15 @@ var _ plugin.SubTaskEntryPoint = CollectBug func CollectBug(taskCtx plugin.SubTaskContext) errors.Error { data := taskCtx.GetData().(*ZentaoTaskData) + if data.Options.ProductId == 0 { + return nil + } collector, err := api.NewApiCollector(api.ApiCollectorArgs{ RawDataSubTaskArgs: api.RawDataSubTaskArgs{ Ctx: taskCtx, Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_BUG_TABLE, @@ -77,4 +79,5 @@ var CollectBugMeta = plugin.SubTaskMeta{ EntryPoint: CollectBug, EnabledByDefault: true, Description: "Collect Bug data from Zentao api", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } diff --git a/backend/plugins/zentao/tasks/bug_convertor.go b/backend/plugins/zentao/tasks/bug_convertor.go index fefd7145f..67f89a932 100644 --- a/backend/plugins/zentao/tasks/bug_convertor.go +++ b/backend/plugins/zentao/tasks/bug_convertor.go @@ -63,7 +63,6 @@ func ConvertBug(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_BUG_TABLE, diff --git a/backend/plugins/zentao/tasks/bug_extractor.go b/backend/plugins/zentao/tasks/bug_extractor.go index e07ed3377..9855d843e 100644 --- a/backend/plugins/zentao/tasks/bug_extractor.go +++ b/backend/plugins/zentao/tasks/bug_extractor.go @@ -43,7 +43,6 @@ func ExtractBug(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_BUG_TABLE, diff --git a/backend/plugins/zentao/tasks/department_collector.go b/backend/plugins/zentao/tasks/department_collector.go index 294673ba2..aa2fc103a 100644 --- a/backend/plugins/zentao/tasks/department_collector.go +++ b/backend/plugins/zentao/tasks/department_collector.go @@ -39,7 +39,6 @@ func CollectDepartment(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_DEPARTMENT_TABLE, @@ -76,4 +75,5 @@ var CollectDepartmentMeta = plugin.SubTaskMeta{ EntryPoint: CollectDepartment, EnabledByDefault: true, Description: "Collect Department data from Zentao api", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } diff --git a/backend/plugins/zentao/tasks/department_convertor.go b/backend/plugins/zentao/tasks/department_convertor.go index 24289b4aa..caa74c1bc 100644 --- a/backend/plugins/zentao/tasks/department_convertor.go +++ b/backend/plugins/zentao/tasks/department_convertor.go @@ -59,7 +59,6 @@ func ConvertDepartment(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_DEPARTMENT_TABLE, diff --git a/backend/plugins/zentao/tasks/department_extractor.go b/backend/plugins/zentao/tasks/department_extractor.go index 0126fe879..e2d389511 100644 --- a/backend/plugins/zentao/tasks/department_extractor.go +++ b/backend/plugins/zentao/tasks/department_extractor.go @@ -43,7 +43,6 @@ func ExtractDepartment(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_DEPARTMENT_TABLE, diff --git a/backend/plugins/zentao/tasks/execution_collector.go b/backend/plugins/zentao/tasks/execution_collector.go index c9c3e123b..1218f7fb3 100644 --- a/backend/plugins/zentao/tasks/execution_collector.go +++ b/backend/plugins/zentao/tasks/execution_collector.go @@ -23,7 +23,6 @@ import ( "github.com/apache/incubator-devlake/core/errors" "github.com/apache/incubator-devlake/core/plugin" "github.com/apache/incubator-devlake/helpers/pluginhelper/api" - "io" "net/http" "net/url" ) @@ -34,7 +33,7 @@ var _ plugin.SubTaskEntryPoint = CollectExecution func CollectExecution(taskCtx plugin.SubTaskContext) errors.Error { data := taskCtx.GetData().(*ZentaoTaskData) - if data.Options.ExecutionId == 0 { + if data.Options.ProjectId == 0 { return nil } collector, err := api.NewApiCollector(api.ApiCollectorArgs{ @@ -43,13 +42,12 @@ func CollectExecution(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_EXECUTION_TABLE, }, ApiClient: data.ApiClient, - UrlTemplate: "executions/{{ .Params.ExecutionId }}", + UrlTemplate: "projects/{{ .Params.ProjectId }}/executions", Query: func(reqData *api.RequestData) (url.Values, errors.Error) { query := url.Values{} query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page)) @@ -57,12 +55,14 @@ func CollectExecution(taskCtx plugin.SubTaskContext) errors.Error { return query, nil }, ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) { - body, err := io.ReadAll(res.Body) + var data struct { + Executions []json.RawMessage `json:"executions"` + } + err := api.UnmarshalResponse(res, &data) if err != nil { - return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao execution collector") + return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao bug collector") } - res.Body.Close() - return []json.RawMessage{body}, nil + return data.Executions, nil }, }) if err != nil { @@ -77,4 +77,5 @@ var CollectExecutionMeta = plugin.SubTaskMeta{ EntryPoint: CollectExecution, EnabledByDefault: true, Description: "Collect Execution data from Zentao api", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } diff --git a/backend/plugins/zentao/tasks/execution_convertor.go b/backend/plugins/zentao/tasks/execution_convertor.go index fbc78d563..4c9866189 100644 --- a/backend/plugins/zentao/tasks/execution_convertor.go +++ b/backend/plugins/zentao/tasks/execution_convertor.go @@ -42,11 +42,11 @@ var ConvertExecutionMeta = plugin.SubTaskMeta{ func ConvertExecutions(taskCtx plugin.SubTaskContext) errors.Error { data := taskCtx.GetData().(*ZentaoTaskData) db := taskCtx.GetDal() - boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoExecution{}) + executionIdGen := didgen.NewDomainIdGenerator(&models.ZentaoExecution{}) + projectIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProject{}) cursor, err := db.Cursor( dal.From(&models.ZentaoExecution{}), - dal.Where(`_tool_zentao_executions.id = ? and - _tool_zentao_executions.connection_id = ?`, data.Options.ExecutionId, data.Options.ConnectionId), + dal.Where(`project_id = ? and connection_id = ?`, data.Options.ProjectId, data.Options.ConnectionId), ) if err != nil { return err @@ -60,7 +60,6 @@ func ConvertExecutions(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_EXECUTION_TABLE, @@ -68,18 +67,37 @@ func ConvertExecutions(taskCtx plugin.SubTaskContext) errors.Error { Convert: func(inputRow interface{}) ([]interface{}, errors.Error) { toolExecution := inputRow.(*models.ZentaoExecution) - domainBoard := &ticket.Board{ + domainStatus := `` + switch toolExecution.Status { + case `wait`: + domainStatus = `FUTURE` + case `doing`: + domainStatus = `ACTIVE` + case `suspended`: + domainStatus = `SUSPENDED` + case `closed`: + case `done`: + domainStatus = `CLOSED` + } + + sprint := &ticket.Sprint{ DomainEntity: domainlayer.DomainEntity{ - Id: boardIdGen.Generate(toolExecution.ConnectionId, toolExecution.Id), + Id: executionIdGen.Generate(toolExecution.ConnectionId, toolExecution.Id), }, - Name: toolExecution.Name, - Description: toolExecution.Description, - Url: toolExecution.Path, - CreatedDate: toolExecution.OpenedDate.ToNullableTime(), - Type: toolExecution.Type, + Name: toolExecution.Name, + Url: toolExecution.Path, + Status: domainStatus, + StartedDate: toolExecution.RealBegan.ToNullableTime(), + EndedDate: toolExecution.RealEnd.ToNullableTime(), + CompletedDate: toolExecution.ClosedDate.ToNullableTime(), + OriginalBoardID: projectIdGen.Generate(toolExecution.ConnectionId, toolExecution.Id), + } + boardSprint := &ticket.BoardSprint{ + BoardId: projectIdGen.Generate(toolExecution.ConnectionId, toolExecution.Id), + SprintId: sprint.Id, } results := make([]interface{}, 0) - results = append(results, domainBoard) + results = append(results, sprint, boardSprint) return results, nil }, }) diff --git a/backend/plugins/zentao/tasks/execution_extractor.go b/backend/plugins/zentao/tasks/execution_extractor.go index 37c835642..f8efa2699 100644 --- a/backend/plugins/zentao/tasks/execution_extractor.go +++ b/backend/plugins/zentao/tasks/execution_extractor.go @@ -43,7 +43,6 @@ func ExtractExecutions(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_EXECUTION_TABLE, @@ -58,6 +57,7 @@ func ExtractExecutions(taskCtx plugin.SubTaskContext) errors.Error { ConnectionId: data.Options.ConnectionId, Id: res.ID, Project: res.Project, + ProjectId: res.Project, Model: res.Model, Type: res.Type, Lifetime: res.Lifetime, diff --git a/backend/plugins/zentao/tasks/product_collector.go b/backend/plugins/zentao/tasks/product_collector.go deleted file mode 100644 index 56fbf839f..000000000 --- a/backend/plugins/zentao/tasks/product_collector.go +++ /dev/null @@ -1,76 +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 tasks - -import ( - "encoding/json" - "fmt" - "github.com/apache/incubator-devlake/core/errors" - "github.com/apache/incubator-devlake/core/plugin" - "github.com/apache/incubator-devlake/helpers/pluginhelper/api" - "io" - "net/http" - "net/url" -) - -const RAW_PRODUCT_TABLE = "zentao_api_products" - -var _ plugin.SubTaskEntryPoint = CollectProduct - -func CollectProduct(taskCtx plugin.SubTaskContext) errors.Error { - data := taskCtx.GetData().(*ZentaoTaskData) - collector, err := api.NewApiCollector(api.ApiCollectorArgs{ - RawDataSubTaskArgs: api.RawDataSubTaskArgs{ - Ctx: taskCtx, - Params: ZentaoApiParams{ - ConnectionId: data.Options.ConnectionId, - ExecutionId: data.Options.ExecutionId, - ProductId: data.Options.ProductId, - ProjectId: data.Options.ProjectId, - }, - Table: RAW_PRODUCT_TABLE, - }, - ApiClient: data.ApiClient, - UrlTemplate: "products/{{ .Params.ProductId }}", - Query: func(reqData *api.RequestData) (url.Values, errors.Error) { - query := url.Values{} - query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page)) - query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size)) - return query, nil - }, - ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) { - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao product collector") - } - res.Body.Close() - return []json.RawMessage{body}, nil - }, - }) - if err != nil { - return err - } - return collector.Execute() -} - -var CollectProductMeta = plugin.SubTaskMeta{ - Name: "CollectProduct", - EntryPoint: CollectProduct, - EnabledByDefault: true, - Description: "Collect Product data from Zentao api", -} diff --git a/backend/plugins/zentao/tasks/product_convertor.go b/backend/plugins/zentao/tasks/product_convertor.go index eeb66baa5..31f64032c 100644 --- a/backend/plugins/zentao/tasks/product_convertor.go +++ b/backend/plugins/zentao/tasks/product_convertor.go @@ -29,6 +29,8 @@ import ( "reflect" ) +const RAW_PRODUCT_TABLE = "zentao_api_products" + var _ plugin.SubTaskEntryPoint = ConvertProducts var ConvertProductMeta = plugin.SubTaskMeta{ @@ -45,8 +47,7 @@ func ConvertProducts(taskCtx plugin.SubTaskContext) errors.Error { boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{}) cursor, err := db.Cursor( dal.From(&models.ZentaoProduct{}), - dal.Where(`_tool_zentao_products.id = ? and - _tool_zentao_products.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId), + dal.Where(`id = ? and connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId), ) if err != nil { return err @@ -59,7 +60,6 @@ func ConvertProducts(taskCtx plugin.SubTaskContext) errors.Error { Ctx: taskCtx, Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, - ExecutionId: data.Options.ExecutionId, ProductId: data.Options.ProductId, ProjectId: data.Options.ProjectId, }, diff --git a/backend/plugins/zentao/tasks/product_extractor.go b/backend/plugins/zentao/tasks/product_extractor.go deleted file mode 100644 index 7cf75d2eb..000000000 --- a/backend/plugins/zentao/tasks/product_extractor.go +++ /dev/null @@ -1,101 +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 tasks - -import ( - "encoding/json" - "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/zentao/models" -) - -var _ plugin.SubTaskEntryPoint = ExtractProducts - -var ExtractProductMeta = plugin.SubTaskMeta{ - Name: "extractProducts", - EntryPoint: ExtractProducts, - EnabledByDefault: true, - Description: "extract Zentao products", - DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, -} - -func ExtractProducts(taskCtx plugin.SubTaskContext) errors.Error { - data := taskCtx.GetData().(*ZentaoTaskData) - extractor, err := api.NewApiExtractor(api.ApiExtractorArgs{ - RawDataSubTaskArgs: api.RawDataSubTaskArgs{ - Ctx: taskCtx, - Params: ZentaoApiParams{ - ConnectionId: data.Options.ConnectionId, - ExecutionId: data.Options.ExecutionId, - ProductId: data.Options.ProductId, - ProjectId: data.Options.ProjectId, - }, - Table: RAW_PRODUCT_TABLE, - }, - Extract: func(row *api.RawData) ([]interface{}, errors.Error) { - res := &models.ZentaoProductRes{} - err := json.Unmarshal(row.Data, res) - if err != nil { - return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao product extractor") - } - product := &models.ZentaoProduct{ - ConnectionId: data.Options.ConnectionId, - Id: int64(res.ID), - Program: res.Program, - Name: res.Name, - Code: res.Code, - Bind: res.Bind, - Line: res.Line, - Type: res.Type, - Status: res.Status, - SubStatus: res.SubStatus, - Description: res.Description, - POId: getAccountId(res.PO), - QDId: getAccountId(res.QD), - RDId: getAccountId(res.RD), - Acl: res.Acl, - Reviewer: res.Reviewer, - CreatedById: getAccountId(res.CreatedBy), - CreatedDate: res.CreatedDate, - CreatedVersion: res.CreatedVersion, - OrderIn: res.OrderIn, - Deleted: res.Deleted, - Plans: res.Plans, - Releases: res.Releases, - Builds: res.Builds, - Cases: res.Cases, - Projects: res.Projects, - Executions: res.Executions, - Bugs: res.Bugs, - Docs: res.Docs, - Progress: res.Progress, - CaseReview: res.CaseReview, - } - results := make([]interface{}, 0) - results = append(results, product) - return results, nil - }, - }) - - if err != nil { - return err - } - - return extractor.Execute() -} diff --git a/backend/plugins/zentao/tasks/project_collector.go b/backend/plugins/zentao/tasks/project_collector.go deleted file mode 100644 index d5f451258..000000000 --- a/backend/plugins/zentao/tasks/project_collector.go +++ /dev/null @@ -1,78 +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 tasks - -import ( - "encoding/json" - "fmt" - "github.com/apache/incubator-devlake/core/errors" - "github.com/apache/incubator-devlake/core/plugin" - "github.com/apache/incubator-devlake/helpers/pluginhelper/api" - "net/http" - "net/url" -) - -const RAW_PROJECT_TABLE = "zentao_api_projects" - -var _ plugin.SubTaskEntryPoint = CollectProject - -func CollectProject(taskCtx plugin.SubTaskContext) errors.Error { - data := taskCtx.GetData().(*ZentaoTaskData) - if data.Options.ProjectId == 0 { - return nil - } - collector, err := api.NewApiCollector(api.ApiCollectorArgs{ - RawDataSubTaskArgs: api.RawDataSubTaskArgs{ - Ctx: taskCtx, - Params: ZentaoApiParams{ - ConnectionId: data.Options.ConnectionId, - ExecutionId: data.Options.ExecutionId, - ProductId: data.Options.ProductId, - ProjectId: data.Options.ProjectId, - }, - Table: RAW_PROJECT_TABLE, - }, - ApiClient: data.ApiClient, - UrlTemplate: "projects", - Query: func(reqData *api.RequestData) (url.Values, errors.Error) { - query := url.Values{} - query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page)) - query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size)) - return query, nil - }, - ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) { - var data struct { - Projects []json.RawMessage `json:"projects"` - } - err := api.UnmarshalResponse(res, &data) - return data.Projects, err - }, - }) - if err != nil { - return err - } - - return collector.Execute() -} - -var CollectProjectMeta = plugin.SubTaskMeta{ - Name: "CollectProject", - EntryPoint: CollectProject, - EnabledByDefault: true, - Description: "Collect Project data from Zentao api", -} diff --git a/backend/plugins/zentao/tasks/product_convertor.go b/backend/plugins/zentao/tasks/project_convertor.go similarity index 69% copy from backend/plugins/zentao/tasks/product_convertor.go copy to backend/plugins/zentao/tasks/project_convertor.go index eeb66baa5..3c2d146ee 100644 --- a/backend/plugins/zentao/tasks/product_convertor.go +++ b/backend/plugins/zentao/tasks/project_convertor.go @@ -29,53 +29,53 @@ import ( "reflect" ) -var _ plugin.SubTaskEntryPoint = ConvertProducts +const RAW_PROJECT_TABLE = "zentao_api_projects" -var ConvertProductMeta = plugin.SubTaskMeta{ - Name: "convertProducts", - EntryPoint: ConvertProducts, +var _ plugin.SubTaskEntryPoint = ConvertProjects + +var ConvertProjectMeta = plugin.SubTaskMeta{ + Name: "convertProjects", + EntryPoint: ConvertProjects, EnabledByDefault: true, - Description: "convert Zentao products", + Description: "convert Zentao projects", DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } -func ConvertProducts(taskCtx plugin.SubTaskContext) errors.Error { +func ConvertProjects(taskCtx plugin.SubTaskContext) errors.Error { data := taskCtx.GetData().(*ZentaoTaskData) db := taskCtx.GetDal() - boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{}) + boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProject{}) cursor, err := db.Cursor( - dal.From(&models.ZentaoProduct{}), - dal.Where(`_tool_zentao_products.id = ? and - _tool_zentao_products.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId), + dal.From(&models.ZentaoProject{}), + dal.Where(`id = ? and connection_id = ?`, data.Options.ProjectId, data.Options.ConnectionId), ) if err != nil { return err } defer cursor.Close() convertor, err := api.NewDataConverter(api.DataConverterArgs{ - InputRowType: reflect.TypeOf(models.ZentaoProduct{}), + InputRowType: reflect.TypeOf(models.ZentaoProject{}), Input: cursor, RawDataSubTaskArgs: api.RawDataSubTaskArgs{ Ctx: taskCtx, Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, - ExecutionId: data.Options.ExecutionId, ProductId: data.Options.ProductId, ProjectId: data.Options.ProjectId, }, - Table: RAW_PRODUCT_TABLE, + Table: RAW_PROJECT_TABLE, }, Convert: func(inputRow interface{}) ([]interface{}, errors.Error) { - toolProduct := inputRow.(*models.ZentaoProduct) + toolProject := inputRow.(*models.ZentaoProject) domainBoard := &ticket.Board{ DomainEntity: domainlayer.DomainEntity{ - Id: boardIdGen.Generate(toolProduct.ConnectionId, toolProduct.Id), + Id: boardIdGen.Generate(toolProject.ConnectionId, toolProject.Id), }, - Name: toolProduct.Name, - Description: toolProduct.Description, - CreatedDate: toolProduct.CreatedDate.ToNullableTime(), - Type: toolProduct.Type, + Name: toolProject.Name, + Description: toolProject.Description, + CreatedDate: toolProject.OpenedDate.ToNullableTime(), + Type: toolProject.Type, } results := make([]interface{}, 0) results = append(results, domainBoard) diff --git a/backend/plugins/zentao/tasks/project_extractor.go b/backend/plugins/zentao/tasks/project_extractor.go deleted file mode 100644 index 319792dc7..000000000 --- a/backend/plugins/zentao/tasks/project_extractor.go +++ /dev/null @@ -1,69 +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 tasks - -import ( - "encoding/json" - "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/zentao/models" -) - -var _ plugin.SubTaskEntryPoint = ExtractProjects - -var ExtractProjectMeta = plugin.SubTaskMeta{ - Name: "extractProjects", - EntryPoint: ExtractProjects, - EnabledByDefault: true, - Description: "extract Zentao projects", - DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, -} - -func ExtractProjects(taskCtx plugin.SubTaskContext) errors.Error { - data := taskCtx.GetData().(*ZentaoTaskData) - extractor, err := api.NewApiExtractor(api.ApiExtractorArgs{ - RawDataSubTaskArgs: api.RawDataSubTaskArgs{ - Ctx: taskCtx, - Params: ZentaoApiParams{ - ConnectionId: data.Options.ConnectionId, - ExecutionId: data.Options.ExecutionId, - ProductId: data.Options.ProductId, - ProjectId: data.Options.ProjectId, - }, - Table: RAW_PROJECT_TABLE, - }, - Extract: func(row *api.RawData) ([]interface{}, errors.Error) { - project := &models.ZentaoProject{} - err := json.Unmarshal(row.Data, project) - if err != nil { - return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao project executor") - } - project.ConnectionId = data.Options.ConnectionId - results := make([]interface{}, 0) - results = append(results, project) - return results, nil - }, - }) - - if err != nil { - return err - } - - return extractor.Execute() -} diff --git a/backend/plugins/zentao/tasks/story_collector.go b/backend/plugins/zentao/tasks/story_collector.go index 6c05b5250..2d9d16617 100644 --- a/backend/plugins/zentao/tasks/story_collector.go +++ b/backend/plugins/zentao/tasks/story_collector.go @@ -33,13 +33,15 @@ var _ plugin.SubTaskEntryPoint = CollectStory func CollectStory(taskCtx plugin.SubTaskContext) errors.Error { data := taskCtx.GetData().(*ZentaoTaskData) + if data.Options.ProductId == 0 { + return nil + } collector, err := api.NewApiCollector(api.ApiCollectorArgs{ RawDataSubTaskArgs: api.RawDataSubTaskArgs{ Ctx: taskCtx, Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_STORY_TABLE, @@ -78,4 +80,5 @@ var CollectStoryMeta = plugin.SubTaskMeta{ EntryPoint: CollectStory, EnabledByDefault: true, Description: "Collect Story data from Zentao api", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } diff --git a/backend/plugins/zentao/tasks/story_convertor.go b/backend/plugins/zentao/tasks/story_convertor.go index aadfc65c1..f80093fbe 100644 --- a/backend/plugins/zentao/tasks/story_convertor.go +++ b/backend/plugins/zentao/tasks/story_convertor.go @@ -47,7 +47,7 @@ func ConvertStory(taskCtx plugin.SubTaskContext) errors.Error { boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{}) cursor, err := db.Cursor( dal.From(&models.ZentaoStory{}), - dal.Where(`_tool_zentao_stories.product = ? and + dal.Where(`_tool_zentao_stories.product = ? and _tool_zentao_stories.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId), ) if err != nil { @@ -62,7 +62,6 @@ func ConvertStory(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_STORY_TABLE, diff --git a/backend/plugins/zentao/tasks/story_extractor.go b/backend/plugins/zentao/tasks/story_extractor.go index cdc6c802f..e014701d2 100644 --- a/backend/plugins/zentao/tasks/story_extractor.go +++ b/backend/plugins/zentao/tasks/story_extractor.go @@ -43,7 +43,6 @@ func ExtractStory(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_STORY_TABLE, diff --git a/backend/plugins/zentao/tasks/task_collector.go b/backend/plugins/zentao/tasks/task_collector.go index 9b7409b70..0547b8a9b 100644 --- a/backend/plugins/zentao/tasks/task_collector.go +++ b/backend/plugins/zentao/tasks/task_collector.go @@ -20,36 +20,58 @@ package tasks import ( "encoding/json" "fmt" + "github.com/apache/incubator-devlake/core/dal" "github.com/apache/incubator-devlake/core/errors" "github.com/apache/incubator-devlake/core/plugin" "github.com/apache/incubator-devlake/helpers/pluginhelper/api" + "github.com/apache/incubator-devlake/plugins/zentao/models" "net/http" "net/url" + "reflect" ) const RAW_TASK_TABLE = "zentao_api_tasks" +type ExecuteInput struct { + Id int64 +} + var _ plugin.SubTaskEntryPoint = CollectTask func CollectTask(taskCtx plugin.SubTaskContext) errors.Error { data := taskCtx.GetData().(*ZentaoTaskData) - if data.Options.ExecutionId == 0 { + if data.Options.ProjectId == 0 { return nil } + cursor, err := taskCtx.GetDal().Cursor( + dal.Select(`id`), + dal.From(&models.ZentaoExecution{}), + dal.Where(`project_id = ? and connection_id = ?`, data.Options.ProjectId, data.Options.ConnectionId), + ) + if err != nil { + return err + } + defer cursor.Close() + + iterator, err := api.NewDalCursorIterator(taskCtx.GetDal(), cursor, reflect.TypeOf(ExecuteInput{})) + if err != nil { + return err + } + collector, err := api.NewApiCollector(api.ApiCollectorArgs{ RawDataSubTaskArgs: api.RawDataSubTaskArgs{ Ctx: taskCtx, Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_TASK_TABLE, }, + Input: iterator, ApiClient: data.ApiClient, PageSize: 100, - UrlTemplate: "/executions/{{ .Params.ExecutionId }}/tasks", + UrlTemplate: "/executions/{{ .Input.Id }}/tasks", Query: func(reqData *api.RequestData) (url.Values, errors.Error) { query := url.Values{} query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page)) @@ -79,4 +101,5 @@ var CollectTaskMeta = plugin.SubTaskMeta{ EntryPoint: CollectTask, EnabledByDefault: true, Description: "Collect Task data from Zentao api", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, } diff --git a/backend/plugins/zentao/tasks/task_convertor.go b/backend/plugins/zentao/tasks/task_convertor.go index 60e9b2ed8..c28271187 100644 --- a/backend/plugins/zentao/tasks/task_convertor.go +++ b/backend/plugins/zentao/tasks/task_convertor.go @@ -48,8 +48,7 @@ func ConvertTask(taskCtx plugin.SubTaskContext) errors.Error { taskIdGen := didgen.NewDomainIdGenerator(&models.ZentaoTask{}) cursor, err := db.Cursor( dal.From(&models.ZentaoTask{}), - dal.Where(`_tool_zentao_tasks.execution = ? and - _tool_zentao_tasks.connection_id = ?`, data.Options.ExecutionId, data.Options.ConnectionId), + dal.Where(`project = ? and connection_id = ?`, data.Options.ProjectId, data.Options.ConnectionId), ) if err != nil { return err @@ -63,7 +62,6 @@ func ConvertTask(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_TASK_TABLE, @@ -103,7 +101,7 @@ func ConvertTask(taskCtx plugin.SubTaskContext) errors.Error { domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes()) } domainBoardIssue := &ticket.BoardIssue{ - BoardId: boardIdGen.Generate(data.Options.ConnectionId, data.Options.ExecutionId), + BoardId: boardIdGen.Generate(data.Options.ConnectionId, toolEntity.Execution), IssueId: domainEntity.Id, } results := make([]interface{}, 0) diff --git a/backend/plugins/zentao/tasks/task_data.go b/backend/plugins/zentao/tasks/task_data.go index 40193f672..83d47e7fe 100644 --- a/backend/plugins/zentao/tasks/task_data.go +++ b/backend/plugins/zentao/tasks/task_data.go @@ -19,6 +19,7 @@ package tasks import ( "fmt" + "github.com/apache/incubator-devlake/core/errors" helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api" "github.com/mitchellh/mapstructure" ) @@ -26,7 +27,6 @@ import ( type ZentaoApiParams struct { ConnectionId uint64 ProductId int64 - ExecutionId int64 ProjectId int64 } @@ -35,11 +35,12 @@ type ZentaoOptions struct { // Such As How many rows do your want // You can use it in subtasks, and you need to pass it to main.go and pipelines. ConnectionId uint64 `json:"connectionId"` - ProductId int64 - ExecutionId int64 - ProjectId int64 - Tasks []string `json:"tasks,omitempty"` - Since string + ProductId int64 `json:"productId" mapstructure:"productId"` + ProjectId int64 `json:"projectId" mapstructure:"projectId"` + // TODO not support now + TimeAfter string `json:"timeAfter" mapstructure:"timeAfter,omitempty"` + //TransformationRuleId uint64 `json:"transformationZentaoeId" mapstructure:"transformationRuleId,omitempty"` + //*models.ZentaoTransformationRule `mapstructure:"transformationRules,omitempty" json:"transformationRules"` } type ZentaoTaskData struct { @@ -57,8 +58,17 @@ func DecodeAndValidateTaskOptions(options map[string]interface{}) (*ZentaoOption if op.ConnectionId == 0 { return nil, fmt.Errorf("connectionId is invalid") } - if op.ProductId == 0 { + if op.ProductId == 0 && op.ProjectId == 0 { return nil, fmt.Errorf("please set productId") } return &op, nil } + +func EncodeTaskOptions(op *ZentaoOptions) (map[string]interface{}, errors.Error) { + var result map[string]interface{} + err := helper.Decode(op, &result, nil) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/backend/plugins/zentao/tasks/task_extractor.go b/backend/plugins/zentao/tasks/task_extractor.go index e8c056425..46988d769 100644 --- a/backend/plugins/zentao/tasks/task_extractor.go +++ b/backend/plugins/zentao/tasks/task_extractor.go @@ -43,7 +43,6 @@ func ExtractTask(taskCtx plugin.SubTaskContext) errors.Error { Params: ZentaoApiParams{ ConnectionId: data.Options.ConnectionId, ProductId: data.Options.ProductId, - ExecutionId: data.Options.ExecutionId, ProjectId: data.Options.ProjectId, }, Table: RAW_TASK_TABLE, diff --git a/backend/plugins/zentao/zentao.go b/backend/plugins/zentao/zentao.go index 11282bf15..fcbbeaa55 100644 --- a/backend/plugins/zentao/zentao.go +++ b/backend/plugins/zentao/zentao.go @@ -31,14 +31,12 @@ func main() { cmd := &cobra.Command{Use: "zentao"} connectionId := cmd.Flags().Uint64P("connectionId", "c", 0, "zentao connection id") - executionId := cmd.Flags().IntP("executionId", "e", 8, "execution id") productId := cmd.Flags().IntP("productId", "o", 8, "product id") projectId := cmd.Flags().IntP("projectId", "p", 8, "project id") cmd.Run = func(cmd *cobra.Command, args []string) { runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{ "connectionId": *connectionId, - "executionId": *executionId, "productId": *productId, "projectId": *projectId, }) diff --git a/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx b/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx index b8d2a8f96..22bbff89e 100644 --- a/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx +++ b/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx @@ -58,7 +58,7 @@ export const useColumns = ({ onDetail }: Props) => { key: 'action', align: 'center', render: (_: any, connection: BPConnectionItemType) => - connection.plugin === 'sonarqube' ? ( + connection.plugin === 'sonarqube' || connection.plugin === 'zentao' ? ( 'No Transformation Required' ) : ( <Button diff --git a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx index d2b52e2a4..d7b042769 100644 --- a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx +++ b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx @@ -162,7 +162,7 @@ export const Configuration = ({ blueprint, operating, onUpdate, onRefresh }: Pro <Icon icon="annotation" color={Colors.BLUE2} /> <span>Change Data Scope</span> </div> - {row.plugin !== 'sonarqube' && ( + {row.plugin !== 'sonarqube' && row.plugin !== 'zentao' && ( <> <div className="item" diff --git a/config-ui/src/pages/pipeline/components/task/index.tsx b/config-ui/src/pages/pipeline/components/task/index.tsx index badfcb9f4..e7b4d3eda 100644 --- a/config-ui/src/pages/pipeline/components/task/index.tsx +++ b/config-ui/src/pages/pipeline/components/task/index.tsx @@ -66,6 +66,13 @@ export const PipelineTask = ({ task }: Props) => { case ['sonarqube'].includes(config.plugin): name = `${name}:${options.projectKey}`; break; + case ['zentao'].includes(config.plugin): + if (options.projectId) { + name = `${name}:project/${options.projectId}`; + } else { + name = `${name}:product/${options.productId}`; + } + break; } return [config.icon, name]; diff --git a/config-ui/src/plugins/components/data-scope/api.ts b/config-ui/src/plugins/components/data-scope/api.ts index 20b12bbc9..dc16c2005 100644 --- a/config-ui/src/plugins/components/data-scope/api.ts +++ b/config-ui/src/plugins/components/data-scope/api.ts @@ -26,3 +26,9 @@ export const updateDataScope = (plugin: string, connectionId: ID, payload: any) method: 'put', data: payload, }); + +export const updateDataScopeWithType = (plugin: string, connectionId: ID, type: string, payload: any) => + request(`/plugins/${plugin}/connections/${connectionId}/${type}/scopes`, { + method: 'put', + data: payload, + }); diff --git a/config-ui/src/plugins/components/data-scope/index.tsx b/config-ui/src/plugins/components/data-scope/index.tsx index 7f86399a9..7beed0529 100644 --- a/config-ui/src/plugins/components/data-scope/index.tsx +++ b/config-ui/src/plugins/components/data-scope/index.tsx @@ -31,6 +31,7 @@ import { MultiSelector } from '@/components'; import type { UseDataScope } from './use-data-scope'; import { useDataScope } from './use-data-scope'; import * as S from './styled'; +import {ZentaoDataScope} from "@/plugins/register/zentao"; interface Props extends UseDataScope { onCancel?: () => void; @@ -70,6 +71,10 @@ export const DataScope = ({ plugin, connectionId, entities, onCancel, ...props } {plugin === 'sonarqube' && ( <SonarQubeDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} /> )} + + {plugin === 'zentao' && ( + <ZentaoDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} /> + )} </div> <div className="block"> diff --git a/config-ui/src/plugins/components/data-scope/use-data-scope.ts b/config-ui/src/plugins/components/data-scope/use-data-scope.ts index 95bae47b9..136f76dcf 100644 --- a/config-ui/src/plugins/components/data-scope/use-data-scope.ts +++ b/config-ui/src/plugins/components/data-scope/use-data-scope.ts @@ -65,6 +65,8 @@ export const useDataScope = ({ plugin, connectionId, entities, initialValues, on return scope.jobFullName; case plugin === 'bitbucket': return scope.bitbucketId; + case plugin === 'zentao': + return scope.type === 'project' ? `project/${scope.id}` : `product/${scope.id}`; case plugin === 'sonarqube': return scope.projectKey; } @@ -85,16 +87,29 @@ export const useDataScope = ({ plugin, connectionId, entities, initialValues, on const handleSave = async () => { const scope = await Promise.all(selectedScope.map((sc: any) => getDataScope(sc))); - const [success, res] = await operator( - () => + let request: () => Promise<any>; + if (plugin === 'zentao') { + request = async () => { + return [ + ...(await API.updateDataScopeWithType(plugin, connectionId, 'product', { + data: scope.filter((s) => s.type !== 'project').map((sc: any) => omit(sc, 'from')), + })), + ...(await API.updateDataScopeWithType(plugin, connectionId, 'project', { + data: scope.filter((s) => s.type === 'project').map((sc: any) => omit(sc, 'from')), + })), + ]; + }; + } else { + request = () => API.updateDataScope(plugin, connectionId, { data: scope.map((sc: any) => omit(sc, 'from')), - }), - { - setOperating: setSaving, - hideToast: true, - }, - ); + }); + } + + const [success, res] = await operator(request, { + setOperating: setSaving, + hideToast: true, + }); if (success) { onSave?.( diff --git a/config-ui/src/plugins/register/zentao/config.ts b/config-ui/src/plugins/register/zentao/config.ts index 2547c53bd..4da05e9cd 100644 --- a/config-ui/src/plugins/register/zentao/config.ts +++ b/config-ui/src/plugins/register/zentao/config.ts @@ -25,7 +25,6 @@ export const ZenTaoConfig: PluginConfigType = { type: PluginType.Connection, plugin: 'zentao', name: 'ZenTao', - isBeta: true, icon: Icon, sort: 100, connection: { diff --git a/config-ui/src/plugins/register/zentao/data-scope.tsx b/config-ui/src/plugins/register/zentao/data-scope.tsx new file mode 100644 index 000000000..601499135 --- /dev/null +++ b/config-ui/src/plugins/register/zentao/data-scope.tsx @@ -0,0 +1,54 @@ +/* + * 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. + * + */ + +import React, { useMemo } from 'react'; + +import { DataScopeMillerColumns } from '@/plugins'; + +import type { ScopeItemType } from './types'; + +interface Props { + connectionId: ID; + selectedItems: ScopeItemType[]; + onChangeItems: (selectedItems: ScopeItemType[]) => void; +} + +export const ZentaoDataScope = ({ connectionId, onChangeItems, ...props }: Props) => { + const selectedItems = useMemo( + () => + props.selectedItems.map((it) => ({ + id: it.type === 'project' ? `project/${it.id}` : `product/${it.id}`, + name: it.name, + data: it, + })), + [props.selectedItems], + ); + + return ( + <> + <h3>Repositories *</h3> + <p>Select the repositories you would like to sync.</p> + <DataScopeMillerColumns + plugin="zentao" + connectionId={connectionId} + selectedItems={selectedItems} + onChangeItems={onChangeItems} + /> + </> + ); +}; diff --git a/config-ui/src/plugins/register/zentao/index.ts b/config-ui/src/plugins/register/zentao/index.ts index de415db39..46ed09889 100644 --- a/config-ui/src/plugins/register/zentao/index.ts +++ b/config-ui/src/plugins/register/zentao/index.ts @@ -17,3 +17,4 @@ */ export * from './config'; +export * from './data-scope'; diff --git a/config-ui/src/plugins/register/zentao/index.ts b/config-ui/src/plugins/register/zentao/types.ts similarity index 81% copy from config-ui/src/plugins/register/zentao/index.ts copy to config-ui/src/plugins/register/zentao/types.ts index de415db39..3b6c23b98 100644 --- a/config-ui/src/plugins/register/zentao/index.ts +++ b/config-ui/src/plugins/register/zentao/types.ts @@ -16,4 +16,10 @@ * */ -export * from './config'; +export type ScopeItemType = { + connectionId: ID; + id: string; + name: string; + type: 'product/normal' | 'product/branch' | 'product/platform' | 'project'; + // and others +};