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

likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 807a5ce99 Issues/4567 zentao bp (#4705)
807a5ce99 is described below

commit 807a5ce99734c13ced99c932cfb36b1dc8aeef2a
Author: Likyh <yang...@meri.co>
AuthorDate: Mon Mar 20 11:09:23 2023 +0800

    Issues/4567 zentao bp (#4705)
    
    * fix: update scope helper
    
    * feat: add bp support for zentao
    
    * feat: support zentao bp in config-ui
    
    * feat: update zentao option
    
    * fix: fix task bug
    
    * fix: update e2e test
    
    * fix: for linter
    
    * fix: for review
---
 .../helpers/pluginhelper/api/remote_api_helper.go  |  30 +++-
 backend/helpers/pluginhelper/api/scope_helper.go   |  14 +-
 backend/plugins/bamboo/api/init.go                 |   4 +-
 backend/plugins/bamboo/models/project.go           |  14 +-
 backend/plugins/zentao/api/blueprint.go            |  67 --------
 backend/plugins/zentao/api/blueprint_V200_test.go  | 175 +++++++++++++++++++++
 backend/plugins/zentao/api/blueprint_v200.go       | 140 +++++++++++++++++
 backend/plugins/zentao/api/init.go                 |  32 ++++
 backend/plugins/zentao/api/remote.go               | 138 ++++++++++++++++
 backend/plugins/zentao/api/scope.go                | 131 +++++++++++++++
 backend/plugins/zentao/e2e/account_test.go         |   1 -
 backend/plugins/zentao/e2e/bug_test.go             |   1 -
 backend/plugins/zentao/e2e/department_test.go      |   1 -
 backend/plugins/zentao/e2e/execution_test.go       |  12 +-
 backend/plugins/zentao/e2e/product_test.go         |  12 +-
 .../e2e/raw_tables/_raw_zentao_api_accounts.csv    |  24 +--
 .../zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv |  12 +-
 .../e2e/raw_tables/_raw_zentao_api_departments.csv |  24 +--
 .../e2e/raw_tables/_raw_zentao_api_executions.csv  |   6 +-
 .../raw_tables/_raw_zentao_api_executions_real.csv |   2 -
 .../e2e/raw_tables/_raw_zentao_api_products.csv    |   4 +-
 .../e2e/raw_tables/_raw_zentao_api_stories.csv     |  18 +--
 .../e2e/raw_tables/_raw_zentao_api_tasks.csv       |  10 +-
 .../snapshot_tables/_tool_zentao_executions.csv    |   3 +-
 .../e2e/snapshot_tables/_tool_zentao_tasks.csv     |   6 +-
 .../e2e/snapshot_tables/boards_execution.csv       |   2 -
 .../e2e/snapshot_tables/execution_board_sprint.csv |   3 +
 .../e2e/snapshot_tables/execution_sprint.csv       |   3 +
 backend/plugins/zentao/e2e/story_test.go           |   1 -
 backend/plugins/zentao/e2e/task_test.go            |   1 -
 backend/plugins/zentao/impl/impl.go                |  27 +++-
 backend/plugins/zentao/models/product.go           | 116 ++++++++++----
 backend/plugins/zentao/models/project.go           |  84 +++++-----
 backend/plugins/zentao/tasks/account_collector.go  |   2 +-
 backend/plugins/zentao/tasks/account_convertor.go  |   1 -
 backend/plugins/zentao/tasks/account_extractor.go  |   1 -
 backend/plugins/zentao/tasks/bug_collector.go      |   5 +-
 backend/plugins/zentao/tasks/bug_convertor.go      |   1 -
 backend/plugins/zentao/tasks/bug_extractor.go      |   1 -
 .../plugins/zentao/tasks/department_collector.go   |   2 +-
 .../plugins/zentao/tasks/department_convertor.go   |   1 -
 .../plugins/zentao/tasks/department_extractor.go   |   1 -
 .../plugins/zentao/tasks/execution_collector.go    |  17 +-
 .../plugins/zentao/tasks/execution_convertor.go    |  42 +++--
 .../plugins/zentao/tasks/execution_extractor.go    |   2 +-
 backend/plugins/zentao/tasks/product_collector.go  |  76 ---------
 backend/plugins/zentao/tasks/product_convertor.go  |   6 +-
 backend/plugins/zentao/tasks/product_extractor.go  | 101 ------------
 backend/plugins/zentao/tasks/project_collector.go  |  78 ---------
 .../{product_convertor.go => project_convertor.go} |  38 ++---
 backend/plugins/zentao/tasks/project_extractor.go  |  69 --------
 backend/plugins/zentao/tasks/story_collector.go    |   5 +-
 backend/plugins/zentao/tasks/story_convertor.go    |   3 +-
 backend/plugins/zentao/tasks/story_extractor.go    |   1 -
 backend/plugins/zentao/tasks/task_collector.go     |  29 +++-
 backend/plugins/zentao/tasks/task_convertor.go     |   6 +-
 backend/plugins/zentao/tasks/task_data.go          |  24 ++-
 backend/plugins/zentao/tasks/task_extractor.go     |   1 -
 backend/plugins/zentao/zentao.go                   |   2 -
 .../blueprint/create/step-three/use-columns.tsx    |   2 +-
 .../pages/blueprint/detail/panel/configuration.tsx |   2 +-
 .../src/pages/pipeline/components/task/index.tsx   |   7 +
 config-ui/src/plugins/components/data-scope/api.ts |   6 +
 .../src/plugins/components/data-scope/index.tsx    |   5 +
 .../components/data-scope/use-data-scope.ts        |  31 +++-
 config-ui/src/plugins/register/zentao/config.ts    |   1 -
 .../src/plugins/register/zentao/data-scope.tsx     |  54 +++++++
 config-ui/src/plugins/register/zentao/index.ts     |   1 +
 .../plugins/register/zentao/{index.ts => types.ts} |   8 +-
 69 files changed, 1103 insertions(+), 647 deletions(-)

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

Reply via email to