You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/03/20 03:09:27 UTC
[incubator-devlake] branch main updated: Issues/4567 zentao bp (#4705)
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 <ya...@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
+};